content: #!/usr/bin/env python
  
  import pytest
  import os
  import shutil
  import json
  import numpy as np
  import cv2
  import sys
  import pandas as pd
  from plotnine import ggplot
  from plantcv import plantcv as pcv
  import plantcv.learn
  import plantcv.parallel
  import plantcv.utils
  # Import matplotlib and use a null Template to block plotting to screen
  # This will let us test debug = "plot"
  import matplotlib
  import matplotlib.pyplot as plt
  import dask
  from dask.distributed import Client
  from skimage import img_as_ubyte
  
  PARALLEL_TEST_DATA = os.path.join(os.path.dirname(os.path.abspath(__file__)), "parallel_data")
  TEST_TMPDIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".cache")
  TEST_IMG_DIR = "images"
  TEST_IMG_DIR2 = "images_w_date"
  TEST_SNAPSHOT_DIR = "snapshots"
  TEST_PIPELINE = os.path.join(PARALLEL_TEST_DATA, "plantcv-script.py")
  META_FIELDS = {"imgtype": 0, "camera": 1, "frame": 2, "zoom": 3, "lifter": 4, "gain": 5, "exposure": 6, "id": 7}
  VALID_META = {
      # Camera settings
      "camera": {
          "label": "camera identifier",
          "datatype": "<class 'str'>",
          "value": "none"
      },
      "imgtype": {
          "label": "image type",
          "datatype": "<class 'str'>",
          "value": "none"
      },
      "zoom": {
          "label": "camera zoom setting",
          "datatype": "<class 'str'>",
          "value": "none"
      },
      "exposure": {
          "label": "camera exposure setting",
          "datatype": "<class 'str'>",
          "value": "none"
      },
      "gain": {
          "label": "camera gain setting",
          "datatype": "<class 'str'>",
          "value": "none"
      },
      "frame": {
          "label": "image series frame identifier",
          "datatype": "<class 'str'>",
          "value": "none"
      },
      "lifter": {
          "label": "imaging platform height setting",
          "datatype": "<class 'str'>",
          "value": "none"
      },
      # Date-Time
      "timestamp": {
          "label": "datetime of image",
          "datatype": "<class 'datetime.datetime'>",
          "value": None
      },
      # Sample attributes
      "id": {
          "label": "image identifier",
          "datatype": "<class 'str'>",
          "value": "none"
      },
      "plantbarcode": {
          "label": "plant barcode identifier",
          "datatype": "<class 'str'>",
          "value": "none"
      },
      "treatment": {
          "label": "treatment identifier",
          "datatype": "<class 'str'>",
          "value": "none"
      },
      "cartag": {
          "label": "plant carrier identifier",
          "datatype": "<class 'str'>",
          "value": "none"
      },
      # Experiment attributes
      "measurementlabel": {
          "label": "experiment identifier",
          "datatype": "<class 'str'>",
          "value": "none"
      },
      # Other
      "other": {
          "label": "other identifier",
          "datatype": "<class 'str'>",
          "value": "none"
      }
  }
  
  METADATA_COPROCESS = {
      'VIS_SV_0_z1_h1_g0_e82_117770.jpg': {
          'path': os.path.join(PARALLEL_TEST_DATA, 'snapshots', 'snapshot57383', 'VIS_SV_0_z1_h1_g0_e82_117770.jpg'),
          'camera': 'SV',
          'imgtype': 'VIS',
          'zoom': 'z1',
          'exposure': 'e82',
          'gain': 'g0',
          'frame': '0',
          'lifter': 'h1',
          'timestamp': '2014-10-22 17:49:35.187',
          'id': '117770',
          'plantbarcode': 'Ca031AA010564',
          'treatment': 'none',
          'cartag': '2143',
          'measurementlabel': 'C002ch_092214_biomass',
          'other': 'none',
          'coimg': 'NIR_SV_0_z1_h1_g0_e65_117779.jpg'
      },
      'NIR_SV_0_z1_h1_g0_e65_117779.jpg': {
          'path': os.path.join(PARALLEL_TEST_DATA, 'snapshots', 'snapshot57383', 'NIR_SV_0_z1_h1_g0_e65_117779.jpg'),
          'camera': 'SV',
          'imgtype': 'NIR',
          'zoom': 'z1',
          'exposure': 'e65',
          'gain': 'g0',
          'frame': '0',
          'lifter': 'h1',
          'timestamp': '2014-10-22 17:49:35.187',
          'id': '117779',
          'plantbarcode': 'Ca031AA010564',
          'treatment': 'none',
          'cartag': '2143',
          'measurementlabel': 'C002ch_092214_biomass',
          'other': 'none'
      }
  }
  METADATA_VIS_ONLY = {
      'VIS_SV_0_z1_h1_g0_e82_117770.jpg': {
          'path': os.path.join(PARALLEL_TEST_DATA, 'snapshots', 'snapshot57383', 'VIS_SV_0_z1_h1_g0_e82_117770.jpg'),
          'camera': 'SV',
          'imgtype': 'VIS',
          'zoom': 'z1',
          'exposure': 'e82',
          'gain': 'g0',
          'frame': '0',
          'lifter': 'h1',
          'timestamp': '2014-10-22 17:49:35.187',
          'id': '117770',
          'plantbarcode': 'Ca031AA010564',
          'treatment': 'none',
          'cartag': '2143',
          'measurementlabel': 'C002ch_092214_biomass',
          'other': 'none'
      }
  }
  METADATA_NIR_ONLY = {
      'NIR_SV_0_z1_h1_g0_e65_117779.jpg': {
          'path': os.path.join(PARALLEL_TEST_DATA, 'snapshots', 'snapshot57383', 'NIR_SV_0_z1_h1_g0_e65_117779.jpg'),
          'camera': 'SV',
          'imgtype': 'NIR',
          'zoom': 'z1',
          'exposure': 'e65',
          'gain': 'g0',
          'frame': '0',
          'lifter': 'h1',
          'timestamp': '2014-10-22 17:49:35.187',
          'id': '117779',
          'plantbarcode': 'Ca031AA010564',
          'treatment': 'none',
          'cartag': '2143',
          'measurementlabel': 'C002ch_092214_biomass',
          'other': 'none'
      }
  }
  # Set the temp directory for dask
  dask.config.set(temporary_directory=TEST_TMPDIR)
  
  
  # ##########################
  # Tests setup function
  # ##########################
  def setup_function():
      if not os.path.exists(TEST_TMPDIR):
          os.mkdir(TEST_TMPDIR)
  
  
  # ##############################
  # Tests for the parallel subpackage
  # ##############################
  def test_plantcv_parallel_workflowconfig_save_config_file():
      # Create a test tmp directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_workflowconfig_save_config_file")
      os.mkdir(cache_dir)
      # Define output path/filename
      template_file = os.path.join(cache_dir, "config.json")
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      # Save template file
      config.save_config(config_file=template_file)
  
      assert os.path.exists(template_file)
  
  
  def test_plantcv_parallel_workflowconfig_import_config_file():
      # Define input path/filename
      config_file = os.path.join(PARALLEL_TEST_DATA, "workflow_config_template.json")
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      # import config file
      config.import_config(config_file=config_file)
  
      assert config.cluster == "LocalCluster"
  
  
  def test_plantcv_parallel_workflowconfig_validate_config():
      # Create a test tmp directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_workflowconfig_validate_config")
      os.mkdir(cache_dir)
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      # Set valid values in config
      config.input_dir = os.path.join(PARALLEL_TEST_DATA, "images")
      config.json = os.path.join(cache_dir, "valid_config.json")
      config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"]
      config.workflow = TEST_PIPELINE
      config.img_outdir = cache_dir
      # Validate config
      assert config.validate_config()
  
  
  def test_plantcv_parallel_workflowconfig_invalid_startdate():
      # Create a test tmp directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_workflowconfig_invalid_startdate")
      os.mkdir(cache_dir)
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      # Set valid values in config
      config.input_dir = os.path.join(PARALLEL_TEST_DATA, "images")
      config.json = os.path.join(cache_dir, "valid_config.json")
      config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"]
      config.workflow = TEST_PIPELINE
      config.img_outdir = cache_dir
      config.start_date = "2020-05-10"
      # Validate config
      assert not config.validate_config()
  
  
  def test_plantcv_parallel_workflowconfig_invalid_enddate():
      # Create a test tmp directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_workflowconfig_invalid_enddate")
      os.mkdir(cache_dir)
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      # Set valid values in config
      config.input_dir = os.path.join(PARALLEL_TEST_DATA, "images")
      config.json = os.path.join(cache_dir, "valid_config.json")
      config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"]
      config.workflow = TEST_PIPELINE
      config.img_outdir = cache_dir
      config.end_date = "2020-05-10"
      config.timestampformat = "%Y%m%d"
      # Validate config
      assert not config.validate_config()
  
  
  def test_plantcv_parallel_workflowconfig_invalid_metadata_terms():
      # Create a test tmp directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_workflowconfig_invalid_metadata_terms")
      os.mkdir(cache_dir)
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      # Set invalid values in config
      # input_dir and json are not defined by default, but are required
      # Set an incorrect metadata term
      config.filename_metadata.append("invalid")
      # Validate config
      assert not config.validate_config()
  
  
  def test_plantcv_parallel_workflowconfig_invalid_filename_metadata():
      # Create a test tmp directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_workflowconfig_invalid_filename_metadata")
      os.mkdir(cache_dir)
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      # Set invalid values in config
      # input_dir and json are not defined by default, but are required
      # Do not set required filename_metadata
      # Validate config
      assert not config.validate_config()
  
  
  def test_plantcv_parallel_workflowconfig_invalid_cluster():
      # Create a test tmp directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_workflowconfig_invalid_cluster")
      os.mkdir(cache_dir)
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      # Set invalid values in config
      # input_dir and json are not defined by default, but are required
      # Set invalid cluster type
      config.cluster = "MyCluster"
      # Validate config
      assert not config.validate_config()
  
  
  def test_plantcv_parallel_metadata_parser_snapshots():
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR)
      config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_snapshots", "output.json")
      config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"]
      config.workflow = TEST_PIPELINE
      config.metadata_filters = {"imgtype": "VIS", "camera": "SV"}
      config.start_date = "2014-10-21 00:00:00.0"
      config.end_date = "2014-10-23 00:00:00.0"
      config.timestampformat = '%Y-%m-%d %H:%M:%S.%f'
      config.imgformat = "jpg"
  
      meta = plantcv.parallel.metadata_parser(config=config)
      assert meta == METADATA_VIS_ONLY
  
  
  def test_plantcv_parallel_metadata_parser_snapshots_coimg():
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR)
      config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_snapshots_coimg", "output.json")
      config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"]
      config.workflow = TEST_PIPELINE
      config.metadata_filters = {"imgtype": "VIS"}
      config.start_date = "2014-10-21 00:00:00.0"
      config.end_date = "2014-10-23 00:00:00.0"
      config.timestampformat = '%Y-%m-%d %H:%M:%S.%f'
      config.imgformat = "jpg"
      config.coprocess = "FAKE"
  
      meta = plantcv.parallel.metadata_parser(config=config)
      assert meta == METADATA_VIS_ONLY
  
  
  def test_plantcv_parallel_metadata_parser_images():
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_IMG_DIR)
      config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_images", "output.json")
      config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"]
      config.workflow = TEST_PIPELINE
      config.metadata_filters = {"imgtype": "VIS"}
      config.start_date = "2014"
      config.end_date = "2014"
      config.timestampformat = '%Y'  # no date in filename so check date range and date_format are ignored
      config.imgformat = "jpg"
  
      meta = plantcv.parallel.metadata_parser(config=config)
      expected = {
          'VIS_SV_0_z1_h1_g0_e82_117770.jpg': {
              'path': os.path.join(PARALLEL_TEST_DATA, 'images', 'VIS_SV_0_z1_h1_g0_e82_117770.jpg'),
              'camera': 'SV',
              'imgtype': 'VIS',
              'zoom': 'z1',
              'exposure': 'e82',
              'gain': 'g0',
              'frame': '0',
              'lifter': 'h1',
              'timestamp': None,
              'id': '117770',
              'plantbarcode': 'none',
              'treatment': 'none',
              'cartag': 'none',
              'measurementlabel': 'none',
              'other': 'none'}
      }
      assert meta == expected
      config.include_all_subdirs = False
      meta = plantcv.parallel.metadata_parser(config=config)
      assert meta == expected
  
  
  def test_plantcv_parallel_metadata_parser_multivalue_filter():
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_IMG_DIR)
      config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_images", "output.json")
      config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"]
      config.workflow = TEST_PIPELINE
      config.metadata_filters = {"imgtype": ["VIS", "NIR"]}
      config.imgformat = "jpg"
  
      meta = plantcv.parallel.metadata_parser(config=config)
      expected = {
          'VIS_SV_0_z1_h1_g0_e82_117770.jpg': {
              'path': os.path.join(PARALLEL_TEST_DATA, TEST_IMG_DIR, 'VIS_SV_0_z1_h1_g0_e82_117770.jpg'),
              'camera': 'SV',
              'imgtype': 'VIS',
              'zoom': 'z1',
              'exposure': 'e82',
              'gain': 'g0',
              'frame': '0',
              'lifter': 'h1',
              'timestamp': None,
              'id': '117770',
              'plantbarcode': 'none',
              'treatment': 'none',
              'cartag': 'none',
              'measurementlabel': 'none',
              'other': 'none'
          },
          'NIR_SV_0_z1_h1_g0_e65_117779.jpg': {
              'path': os.path.join(PARALLEL_TEST_DATA, TEST_IMG_DIR, 'NIR_SV_0_z1_h1_g0_e65_117779.jpg'),
              'camera': 'SV',
              'imgtype': 'NIR',
              'zoom': 'z1',
              'exposure': 'e65',
              'gain': 'g0',
              'frame': '0',
              'lifter': 'h1',
              'timestamp': None,
              'id': '117779',
              'plantbarcode': 'none',
              'treatment': 'none',
              'cartag': 'none',
              'measurementlabel': 'none',
              'other': 'none'
          }
      }
      assert meta == expected
  
  
  def test_plantcv_parallel_metadata_parser_multivalue_filter_nomatch():
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_IMG_DIR)
      config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_images", "output.json")
      config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"]
      config.workflow = TEST_PIPELINE
      config.metadata_filters = {"imgtype": ["VIS", "PSII"]}
      config.imgformat = "jpg"
  
      meta = plantcv.parallel.metadata_parser(config=config)
      expected = {
          'VIS_SV_0_z1_h1_g0_e82_117770.jpg': {
              'path': os.path.join(PARALLEL_TEST_DATA, TEST_IMG_DIR, 'VIS_SV_0_z1_h1_g0_e82_117770.jpg'),
              'camera': 'SV',
              'imgtype': 'VIS',
              'zoom': 'z1',
              'exposure': 'e82',
              'gain': 'g0',
              'frame': '0',
              'lifter': 'h1',
              'timestamp': None,
              'id': '117770',
              'plantbarcode': 'none',
              'treatment': 'none',
              'cartag': 'none',
              'measurementlabel': 'none',
              'other': 'none'
          }
      }
      assert meta == expected
  
  
  def test_plantcv_parallel_metadata_parser_regex():
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_IMG_DIR)
      config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_images", "output.json")
      config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"]
      config.workflow = TEST_PIPELINE
      config.metadata_filters = {"imgtype": "VIS"}
      config.start_date = "2014-10-21 00:00:00.0"
      config.end_date = "2014-10-23 00:00:00.0"
      config.timestampformat = '%Y-%m-%d %H:%M:%S.%f'
      config.imgformat = "jpg"
      config.delimiter = r'(VIS)_(SV)_(\d+)_(z1)_(h1)_(g0)_(e82)_(\d+)'
  
      meta = plantcv.parallel.metadata_parser(config=config)
      expected = {
          'VIS_SV_0_z1_h1_g0_e82_117770.jpg': {
              'path': os.path.join(PARALLEL_TEST_DATA, 'images', 'VIS_SV_0_z1_h1_g0_e82_117770.jpg'),
              'camera': 'SV',
              'imgtype': 'VIS',
              'zoom': 'z1',
              'exposure': 'e82',
              'gain': 'g0',
              'frame': '0',
              'lifter': 'h1',
              'timestamp': None,
              'id': '117770',
              'plantbarcode': 'none',
              'treatment': 'none',
              'cartag': 'none',
              'measurementlabel': 'none',
              'other': 'none'}
      }
      assert meta == expected
  
  
  def test_plantcv_parallel_metadata_parser_images_outside_daterange():
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_IMG_DIR2)
      config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_images_outside_daterange",
                                 "output.json")
      config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "timestamp"]
      config.workflow = TEST_PIPELINE
      config.metadata_filters = {"imgtype": "NIR"}
      config.start_date = "1970-01-01 00_00_00"
      config.end_date = "1970-01-01 00_00_00"
      config.timestampformat = "%Y-%m-%d %H_%M_%S"
      config.imgformat = "jpg"
      config.delimiter = r"(NIR)_(SV)_(\d)_(z1)_(h1)_(g0)_(e65)_(\d{4}-\d{2}-\d{2} \d{2}_\d{2}_\d{2})"
  
      meta = plantcv.parallel.metadata_parser(config=config)
      assert meta == {}
  
  
  def test_plantcv_parallel_metadata_parser_no_default_dates():
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR)
      config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_no_default_dates", "output.json")
      config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"]
      config.workflow = TEST_PIPELINE
      config.metadata_filters = {"imgtype": "VIS", "camera": "SV", "id": "117770"}
      config.start_date = None
      config.end_date = None
      config.timestampformat = '%Y-%m-%d %H:%M:%S.%f'
      config.imgformat = "jpg"
  
      meta = plantcv.parallel.metadata_parser(config=config)
      assert meta == METADATA_VIS_ONLY
  
  
  def test_plantcv_parallel_workflowconfig_subdaily_timestampformat():
      '''
      timestampformats with only hours and smaller units of time were failing if the script was run earlier in the day than the images were taken. this was fixed by setting end_date to 23-59-59 if we don't detect the year-month-day
      '''
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_IMG_DIR2)
      config.json = os.path.join(TEST_IMG_DIR2, "test_plantcv_parallel_metadata_parser_subdaily_timestampformat", "output.json")
      config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "timestamp"]
      config.workflow = TEST_PIPELINE
      config.metadata_filters = {"imgtype": "NIR", "camera": "SV"}
      config.start_date = None
      config.end_date = None
      config.timestampformat = "%H_%M_%S"
      config.imgformat = "jpg"
  
      config.delimiter = r"(NIR)_(SV)_(\d)_(z1)_(h1)_(g0)_(e65)_(\d{2}_\d{2}_\d{2})"
  
      meta = plantcv.parallel.metadata_parser(config=config)
      assert meta == {
          'NIR_SV_0_z1_h1_g0_e65_23_59_59.jpg': {
              'path': os.path.join(PARALLEL_TEST_DATA, 'images_w_date','NIR_SV_0_z1_h1_g0_e65_23_59_59.jpg'),
              'imgtype': 'NIR',
              'camera': 'SV',
              'frame': '0',
              'zoom': 'z1',
              'lifter': 'h1',
              'gain': 'g0',
              'exposure': 'e65',
              'timestamp': '23_59_59',
              'measurementlabel': 'none',
              'cartag':'none',
              'id': 'none',
              'treatment': 'none',
              'plantbarcode': 'none',
              'other': 'none'
          }
      }
  
      
  def test_plantcv_parallel_check_date_range_wrongdateformat():
      start_date = 10
      end_date = 10
      img_time = '2010-10-10'
  
      with pytest.raises(SystemExit, match=r'does not match format'):
          date_format = '%Y%m%d'
          _ = plantcv.parallel.check_date_range(
              start_date, end_date, img_time, date_format)
  
  
  def test_plantcv_parallel_metadata_parser_snapshot_outside_daterange():
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR)
      config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_snapshot_outside_daterange",
                                 "output.json")
      config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"]
      config.workflow = TEST_PIPELINE
      config.metadata_filters = {"imgtype": "VIS"}
      config.start_date = "1970-01-01 00:00:00.0"
      config.end_date = "1970-01-01 00:00:00.0"
      config.timestampformat = '%Y-%m-%d %H:%M:%S.%f'
      config.imgformat = "jpg"
  
      meta = plantcv.parallel.metadata_parser(config=config)
  
      assert meta == {}
  
  
  def test_plantcv_parallel_metadata_parser_fail_images():
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR)
      config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_fail_images", "output.json")
      config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"]
      config.workflow = TEST_PIPELINE
      config.metadata_filters = {"cartag": "VIS"}
      config.start_date = "1970-01-01 00:00:00.0"
      config.end_date = "1970-01-01 00:00:00.0"
      config.timestampformat = '%Y-%m-%d %H:%M:%S.%f'
      config.imgformat = "jpg"
      config.coprocess = "NIR"
  
      meta = plantcv.parallel.metadata_parser(config=config)
      assert meta == METADATA_NIR_ONLY
  
  
  def test_plantcv_parallel_metadata_parser_images_with_frame():
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR)
      config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_images_with_frame", "output.json")
      config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"]
      config.workflow = TEST_PIPELINE
      config.metadata_filters = {"imgtype": "VIS"}
      config.start_date = "2014-10-21 00:00:00.0"
      config.end_date = "2014-10-23 00:00:00.0"
      config.timestampformat = '%Y-%m-%d %H:%M:%S.%f'
      config.imgformat = "jpg"
      config.coprocess = "NIR"
  
      meta = plantcv.parallel.metadata_parser(config=config)
  
      assert meta == {
          'VIS_SV_0_z1_h1_g0_e82_117770.jpg': {
              'path': os.path.join(PARALLEL_TEST_DATA, 'snapshots', 'snapshot57383', 'VIS_SV_0_z1_h1_g0_e82_117770.jpg'),
              'camera': 'SV',
              'imgtype': 'VIS',
              'zoom': 'z1',
              'exposure': 'e82',
              'gain': 'g0',
              'frame': '0',
              'lifter': 'h1',
              'timestamp': '2014-10-22 17:49:35.187',
              'id': '117770',
              'plantbarcode': 'Ca031AA010564',
              'treatment': 'none',
              'cartag': '2143',
              'measurementlabel': 'C002ch_092214_biomass',
              'other': 'none',
              'coimg': 'NIR_SV_0_z1_h1_g0_e65_117779.jpg'
          },
          'NIR_SV_0_z1_h1_g0_e65_117779.jpg': {
              'path': os.path.join(PARALLEL_TEST_DATA, 'snapshots', 'snapshot57383', 'NIR_SV_0_z1_h1_g0_e65_117779.jpg'),
              'camera': 'SV',
              'imgtype': 'NIR',
              'zoom': 'z1',
              'exposure': 'e65',
              'gain': 'g0',
              'frame': '0',
              'lifter': 'h1',
              'timestamp': '2014-10-22 17:49:35.187',
              'id': '117779',
              'plantbarcode': 'Ca031AA010564',
              'treatment': 'none',
              'cartag': '2143',
              'measurementlabel': 'C002ch_092214_biomass',
              'other': 'none'
          }
      }
  
  
  def test_plantcv_parallel_metadata_parser_images_no_frame():
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR)
      config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_images_no_frame",
                                 "output.json")
      config.filename_metadata = ["imgtype", "camera", "X", "zoom", "lifter", "gain", "exposure", "id"]
      config.workflow = TEST_PIPELINE
      config.metadata_filters = {"imgtype": "VIS"}
      config.start_date = "2014-10-21 00:00:00.0"
      config.end_date = "2014-10-23 00:00:00.0"
      config.timestampformat = '%Y-%m-%d %H:%M:%S.%f'
      config.imgformat = "jpg"
      config.coprocess = "NIR"
  
      meta = plantcv.parallel.metadata_parser(config=config)
  
      assert meta == {
          'VIS_SV_0_z1_h1_g0_e82_117770.jpg': {
              'path': os.path.join(PARALLEL_TEST_DATA, 'snapshots', 'snapshot57383', 'VIS_SV_0_z1_h1_g0_e82_117770.jpg'),
              'camera': 'SV',
              'imgtype': 'VIS',
              'zoom': 'z1',
              'exposure': 'e82',
              'gain': 'g0',
              'frame': 'none',
              'lifter': 'h1',
              'timestamp': '2014-10-22 17:49:35.187',
              'id': '117770',
              'plantbarcode': 'Ca031AA010564',
              'treatment': 'none',
              'cartag': '2143',
              'measurementlabel': 'C002ch_092214_biomass',
              'other': 'none',
              'coimg': 'NIR_SV_0_z1_h1_g0_e65_117779.jpg'
          },
          'NIR_SV_0_z1_h1_g0_e65_117779.jpg': {
              'path': os.path.join(PARALLEL_TEST_DATA, 'snapshots', 'snapshot57383', 'NIR_SV_0_z1_h1_g0_e65_117779.jpg'),
              'camera': 'SV',
              'imgtype': 'NIR',
              'zoom': 'z1',
              'exposure': 'e65',
              'gain': 'g0',
              'frame': 'none',
              'lifter': 'h1',
              'timestamp': '2014-10-22 17:49:35.187',
              'id': '117779',
              'plantbarcode': 'Ca031AA010564',
              'treatment': 'none',
              'cartag': '2143',
              'measurementlabel': 'C002ch_092214_biomass',
              'other': 'none'
          }
      }
  
  
  def test_plantcv_parallel_metadata_parser_images_no_camera():
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR)
      config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_images_no_frame", "output.json")
      config.filename_metadata = ["imgtype", "X", "frame", "zoom", "lifter", "gain", "exposure", "id"]
      config.workflow = TEST_PIPELINE
      config.metadata_filters = {"imgtype": "VIS"}
      config.start_date = "2014-10-21 00:00:00.0"
      config.end_date = "2014-10-23 00:00:00.0"
      config.timestampformat = '%Y-%m-%d %H:%M:%S.%f'
      config.imgformat = "jpg"
      config.coprocess = "NIR"
  
      meta = plantcv.parallel.metadata_parser(config=config)
  
      assert meta == {
          'VIS_SV_0_z1_h1_g0_e82_117770.jpg': {
              'path': os.path.join(PARALLEL_TEST_DATA, 'snapshots', 'snapshot57383', 'VIS_SV_0_z1_h1_g0_e82_117770.jpg'),
              'camera': 'none',
              'imgtype': 'VIS',
              'zoom': 'z1',
              'exposure': 'e82',
              'gain': 'g0',
              'frame': '0',
              'lifter': 'h1',
              'timestamp': '2014-10-22 17:49:35.187',
              'id': '117770',
              'plantbarcode': 'Ca031AA010564',
              'treatment': 'none',
              'cartag': '2143',
              'measurementlabel': 'C002ch_092214_biomass',
              'other': 'none',
              'coimg': 'NIR_SV_0_z1_h1_g0_e65_117779.jpg'
          },
          'NIR_SV_0_z1_h1_g0_e65_117779.jpg': {
              'path': os.path.join(PARALLEL_TEST_DATA, 'snapshots', 'snapshot57383', 'NIR_SV_0_z1_h1_g0_e65_117779.jpg'),
              'camera': 'none',
              'imgtype': 'NIR',
              'zoom': 'z1',
              'exposure': 'e65',
              'gain': 'g0',
              'frame': '0',
              'lifter': 'h1',
              'timestamp': '2014-10-22 17:49:35.187',
              'id': '117779',
              'plantbarcode': 'Ca031AA010564',
              'treatment': 'none',
              'cartag': '2143',
              'measurementlabel': 'C002ch_092214_biomass',
              'other': 'none'
          }
      }
  
  
  def test_plantcv_parallel_job_builder_single_image():
      # Create cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_job_builder_single_image")
      os.mkdir(cache_dir)
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR)
      config.json = os.path.join(cache_dir, "output.json")
      config.tmp_dir = cache_dir
      config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"]
      config.workflow = TEST_PIPELINE
      config.img_outdir = cache_dir
      config.metadata_filters = {"imgtype": "VIS", "camera": "SV"}
      config.start_date = "2014-10-21 00:00:00.0"
      config.end_date = "2014-10-23 00:00:00.0"
      config.timestampformat = '%Y-%m-%d %H:%M:%S.%f'
      config.imgformat = "jpg"
      config.other_args = ["--other", "on"]
      config.writeimg = True
  
      jobs = plantcv.parallel.job_builder(meta=METADATA_VIS_ONLY, config=config)
  
      image_name = list(METADATA_VIS_ONLY.keys())[0]
      result_file = os.path.join(cache_dir, image_name + '.txt')
  
      expected = ['python', TEST_PIPELINE, '--image', METADATA_VIS_ONLY[image_name]['path'], '--outdir',
                  cache_dir, '--result', result_file, '--writeimg', '--other', 'on']
  
      if len(expected) != len(jobs[0]):
          assert False
      else:
          assert all([i == j] for i, j in zip(jobs[0], expected))
  
  
  def test_plantcv_parallel_job_builder_coprocess():
      # Create cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_job_builder_coprocess")
      os.mkdir(cache_dir)
      # Create config instance
      config = plantcv.parallel.WorkflowConfig()
      config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR)
      config.json = os.path.join(cache_dir, "output.json")
      config.tmp_dir = cache_dir
      config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"]
      config.workflow = TEST_PIPELINE
      config.img_outdir = cache_dir
      config.metadata_filters = {"imgtype": "VIS", "camera": "SV"}
      config.start_date = "2014-10-21 00:00:00.0"
      config.end_date = "2014-10-23 00:00:00.0"
      config.timestampformat = '%Y-%m-%d %H:%M:%S.%f'
      config.imgformat = "jpg"
      config.other_args = ["--other", "on"]
      config.writeimg = True
      config.coprocess = "NIR"
  
      jobs = plantcv.parallel.job_builder(meta=METADATA_COPROCESS, config=config)
  
      img_names = list(METADATA_COPROCESS.keys())
      vis_name = img_names[0]
      vis_path = METADATA_COPROCESS[vis_name]['path']
      result_file = os.path.join(cache_dir, vis_name + '.txt')
      nir_name = img_names[1]
      coresult_file = os.path.join(cache_dir, nir_name + '.txt')
  
      expected = ['python', TEST_PIPELINE, '--image', vis_path, '--outdir', cache_dir, '--result', result_file,
                  '--coresult', coresult_file, '--writeimg', '--other', 'on']
  
      if len(expected) != len(jobs[0]):
          assert False
      else:
          assert all([i == j] for i, j in zip(jobs[0], expected))
  
  
  def test_plantcv_parallel_multiprocess_create_dask_cluster_local():
      client = plantcv.parallel.create_dask_cluster(cluster="LocalCluster", cluster_config={})
      status = client.status
      client.shutdown()
      assert status == "running"
  
  
  def test_plantcv_parallel_multiprocess_create_dask_cluster():
      client = plantcv.parallel.create_dask_cluster(cluster="HTCondorCluster", cluster_config={"cores": 1,
                                                                                               "memory": "1GB",
                                                                                               "disk": "1GB"})
      status = client.status
      client.shutdown()
      assert status == "running"
  
  
  def test_plantcv_parallel_multiprocess_create_dask_cluster_invalid_cluster():
      with pytest.raises(ValueError):
          _ = plantcv.parallel.create_dask_cluster(cluster="Skynet", cluster_config={})
  
  
  def test_plantcv_parallel_convert_datetime_to_unixtime():
      unix_time = plantcv.parallel.convert_datetime_to_unixtime(timestamp_str="1970-01-01", date_format="%Y-%m-%d")
      assert unix_time == 0
  
  
  def test_plantcv_parallel_convert_datetime_to_unixtime_bad_strptime():
      with pytest.raises(SystemExit):
          _ = plantcv.parallel.convert_datetime_to_unixtime(timestamp_str="1970-01-01", date_format="%Y-%m")
  
  
  def test_plantcv_parallel_multiprocess():
      image_name = list(METADATA_VIS_ONLY.keys())[0]
      image_path = os.path.join(METADATA_VIS_ONLY[image_name]['path'], image_name)
      result_file = os.path.join(TEST_TMPDIR, image_name + '.txt')
      jobs = [['python', TEST_PIPELINE, '--image', image_path, '--outdir', TEST_TMPDIR, '--result', result_file,
               '--writeimg', '--other', 'on']]
      # Create a dask LocalCluster client
      client = Client(n_workers=1)
      plantcv.parallel.multiprocess(jobs, client=client)
      assert os.path.exists(result_file)
  
  
  def test_plantcv_parallel_process_results():
      # Create a test tmp directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_process_results")
      os.mkdir(cache_dir)
      plantcv.parallel.process_results(job_dir=os.path.join(PARALLEL_TEST_DATA, "results"),
                                       json_file=os.path.join(cache_dir, 'appended_results.json'))
      plantcv.parallel.process_results(job_dir=os.path.join(PARALLEL_TEST_DATA, "results"),
                                       json_file=os.path.join(cache_dir, 'appended_results.json'))
      # Assert that the output JSON file matches the expected output JSON file
      result_file = open(os.path.join(cache_dir, "appended_results.json"), "r")
      results = json.load(result_file)
      result_file.close()
      expected_file = open(os.path.join(PARALLEL_TEST_DATA, "appended_results.json"))
      expected = json.load(expected_file)
      expected_file.close()
      assert results == expected
  
  
  def test_plantcv_parallel_process_results_new_output():
      # Create a test tmp directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_process_results_new_output")
      os.mkdir(cache_dir)
      plantcv.parallel.process_results(job_dir=os.path.join(PARALLEL_TEST_DATA, "results"),
                                       json_file=os.path.join(cache_dir, 'new_result.json'))
      # Assert output matches expected values
      result_file = open(os.path.join(cache_dir, "new_result.json"), "r")
      results = json.load(result_file)
      result_file.close()
      expected_file = open(os.path.join(PARALLEL_TEST_DATA, "new_result.json"))
      expected = json.load(expected_file)
      expected_file.close()
      assert results == expected
  
  
  def test_plantcv_parallel_process_results_valid_json():
      # Test when the file is a valid json file but doesn't contain expected keys
      with pytest.raises(RuntimeError):
          plantcv.parallel.process_results(job_dir=os.path.join(PARALLEL_TEST_DATA, "results"),
                                           json_file=os.path.join(PARALLEL_TEST_DATA, "valid.json"))
  
  
  def test_plantcv_parallel_process_results_invalid_json():
      # Create a test tmp directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_process_results_invalid_json")
      os.mkdir(cache_dir)
      # Move the test data to the tmp directory
      shutil.copytree(os.path.join(PARALLEL_TEST_DATA, "bad_results"), os.path.join(cache_dir, "bad_results"))
      with pytest.raises(RuntimeError):
          plantcv.parallel.process_results(job_dir=os.path.join(cache_dir, "bad_results"),
                                           json_file=os.path.join(cache_dir, "bad_results", "invalid.txt"))
  
  
  # ####################################################################################################################
  # ########################################### PLANTCV MAIN PACKAGE ###################################################
  matplotlib.use('Template')
  
  TEST_DATA = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data")
  HYPERSPECTRAL_TEST_DATA = os.path.join(os.path.dirname(os.path.abspath(__file__)), "hyperspectral_data")
  HYPERSPECTRAL_DATA = "darkReference"
  HYPERSPECTRAL_WHITE = "darkReference_whiteReference"
  HYPERSPECTRAL_DARK = "darkReference_darkReference"
  HYPERSPECTRAL_HDR = "darkReference.hdr"
  HYPERSPECTRAL_MASK = "darkReference_mask.png"
  HYPERSPECTRAL_DATA_NO_DEFAULT = "darkReference2"
  HYPERSPECTRAL_HDR_NO_DEFAULT = "darkReference2.hdr"
  HYPERSPECTRAL_DATA_APPROX_PSEUDO = "darkReference3"
  HYPERSPECTRAL_HDR_APPROX_PSEUDO = "darkReference3.hdr"
  HYPERSPECTRAL_DATA_BAD_INTERLEAVE = "darkReference4"
  HYPERSPECTRAL_HDR_BAD_INTERLEAVE = "darkReference4.hdr"
  HYPERSPECTRAL_HDR_SMALL_RANGE = {'description': '{[HEADWALL Hyperspec III]}', 'samples': '800', 'lines': '1',
                                   'bands': '978', 'header offset': '0', 'file type': 'ENVI Standard',
                                   'interleave': 'bil', 'sensor type': 'Unknown', 'byte order': '0',
                                   'default bands': '159,253,520', 'wavelength units': 'nm',
                                   'wavelength': ['379.027', '379.663', '380.3', '380.936', '381.573', '382.209']}
  FLUOR_TEST_DATA = os.path.join(os.path.dirname(os.path.abspath(__file__)), "photosynthesis_data")
  FLUOR_IMG = "PSII_PSD_supopt_temp_btx623_22_rep1.DAT"
  TEST_COLOR_DIM = (2056, 2454, 3)
  TEST_GRAY_DIM = (2056, 2454)
  TEST_BINARY_DIM = TEST_GRAY_DIM
  TEST_INPUT_COLOR = "input_color_img.jpg"
  TEST_INPUT_GRAY = "input_gray_img.jpg"
  TEST_INPUT_GRAY_SMALL = "input_gray_img_small.jpg"
  TEST_INPUT_BINARY = "input_binary_img.png"
  # Image from http://www.libpng.org/pub/png/png-OwlAlpha.html
  # This image may be used, edited and reproduced freely.
  TEST_INPUT_RGBA = "input_rgba.png"
  TEST_INPUT_BAYER = "bayer_img.png"
  TEST_INPUT_ROI_CONTOUR = "input_roi_contour.npz"
  TEST_INPUT_ROI_HIERARCHY = "input_roi_hierarchy.npz"
  TEST_INPUT_CONTOURS = "input_contours.npz"
  TEST_INPUT_OBJECT_CONTOURS = "input_object_contours.npz"
  TEST_INPUT_OBJECT_HIERARCHY = "input_object_hierarchy.npz"
  TEST_VIS = "VIS_SV_0_z300_h1_g0_e85_v500_93054.png"
  TEST_NIR = "NIR_SV_0_z300_h1_g0_e15000_v500_93059.png"
  TEST_VIS_TV = "VIS_TV_0_z300_h1_g0_e85_v500_93054.png"
  TEST_NIR_TV = "NIR_TV_0_z300_h1_g0_e15000_v500_93059.png"
  TEST_INPUT_MASK = "input_mask_binary.png"
  TEST_INPUT_MASK_OOB = "mask_outbounds.png"
  TEST_INPUT_MASK_RESIZE = "input_mask_resize.png"
  TEST_INPUT_NIR_MASK = "input_nir.png"
  TEST_INPUT_FDARK = "FLUO_TV_dark.png"
  TEST_INPUT_FDARK_LARGE = "FLUO_TV_DARK_large"
  TEST_INPUT_FMIN = "FLUO_TV_min.png"
  TEST_INPUT_FMAX = "FLUO_TV_max.png"
  TEST_INPUT_FMASK = "FLUO_TV_MASK.png"
  TEST_INPUT_GREENMAG = "input_green-magenta.jpg"
  TEST_INPUT_MULTI = "multi_ori_image.jpg"
  TEST_INPUT_MULTI_MASK = "multi_ori_mask.jpg"
  TEST_INPUT_MULTI_OBJECT = "roi_objects.npz"
  TEST_INPUT_MULTI_CONTOUR = "multi_contours.npz"
  TEST_INPUT_ClUSTER_CONTOUR = "clusters_i.npz"
  TEST_INPUT_MULTI_HIERARCHY = "multi_hierarchy.npz"
  TEST_INPUT_VISUALIZE_CONTOUR = "roi_objects_visualize.npz"
  TEST_INPUT_VISUALIZE_HIERARCHY = "roi_obj_hierarchy_visualize.npz"
  TEST_INPUT_VISUALIZE_CLUSTERS = "clusters_i_visualize.npz"
  TEST_INPUT_VISUALIZE_BACKGROUND = "visualize_background_img.png"
  TEST_INPUT_GENOTXT = "cluster_names.txt"
  TEST_INPUT_GENOTXT_TOO_MANY = "cluster_names_too_many.txt"
  TEST_INPUT_CROPPED = 'cropped_img.jpg'
  TEST_INPUT_CROPPED_MASK = 'cropped-mask.png'
  TEST_INPUT_MARKER = 'seed-image.jpg'
  TEST_INPUT_SKELETON = 'input_skeleton.png'
  TEST_INPUT_SKELETON_PRUNED = 'input_pruned_skeleton.png'
  TEST_FOREGROUND = "TEST_FOREGROUND.jpg"
  TEST_BACKGROUND = "TEST_BACKGROUND.jpg"
  TEST_PDFS = "naive_bayes_pdfs.txt"
  TEST_PDFS_BAD = "naive_bayes_pdfs_bad.txt"
  TEST_VIS_SMALL = "setaria_small_vis.png"
  TEST_MASK_SMALL = "setaria_small_mask.png"
  TEST_VIS_COMP_CONTOUR = "setaria_composed_contours.npz"
  TEST_ACUTE_RESULT = np.asarray([[[119, 285]], [[151, 280]], [[168, 267]], [[168, 262]], [[171, 261]], [[224, 269]],
                                  [[246, 271]], [[260, 277]], [[141, 248]], [[183, 194]], [[188, 237]], [[173, 240]],
                                  [[186, 260]], [[147, 244]], [[163, 246]], [[173, 268]], [[170, 272]], [[151, 320]],
                                  [[195, 289]], [[228, 272]], [[210, 272]], [[209, 247]], [[210, 232]]])
  TEST_VIS_SMALL_PLANT = "setaria_small_plant_vis.png"
  TEST_MASK_SMALL_PLANT = "setaria_small_plant_mask.png"
  TEST_VIS_COMP_CONTOUR_SMALL_PLANT = "setaria_small_plant_composed_contours.npz"
  TEST_SAMPLED_RGB_POINTS = "sampled_rgb_points.txt"
  TEST_TARGET_IMG = "target_img.png"
  TEST_TARGET_IMG_WITH_HEXAGON = "target_img_w_hexagon.png"
  TEST_TARGET_IMG_TRIANGLE = "target_img copy.png"
  TEST_SOURCE1_IMG = "source1_img.png"
  TEST_SOURCE2_IMG = "source2_img.png"
  TEST_TARGET_MASK = "mask_img.png"
  TEST_TARGET_IMG_COLOR_CARD = "color_card_target.png"
  TEST_SOURCE2_MASK = "mask2_img.png"
  TEST_TARGET_MATRIX = "target_matrix.npz"
  TEST_SOURCE1_MATRIX = "source1_matrix.npz"
  TEST_SOURCE2_MATRIX = "source2_matrix.npz"
  TEST_MATRIX_B1 = "matrix_b1.npz"
  TEST_MATRIX_B2 = "matrix_b2.npz"
  TEST_TRANSFORM1 = "transformation_matrix1.npz"
  TEST_MATRIX_M1 = "matrix_m1.npz"
  TEST_MATRIX_M2 = "matrix_m2.npz"
  TEST_S1_CORRECTED = "source_corrected.png"
  TEST_SKELETON_OBJECTS = "skeleton_objects.npz"
  TEST_SKELETON_HIERARCHIES = "skeleton_hierarchies.npz"
  TEST_THERMAL_ARRAY = "thermal_img.npz"
  TEST_THERMAL_IMG_MASK = "thermal_img_mask.png"
  TEST_INPUT_THERMAL_CSV = "FLIR2600.csv"
  # TEST_BAD_MASK = "bad_mask_test.pkl"
  # TEST_IM_BAD_NONE = "bad_mask_none.pkl"
  # TEST_IM_BAD_BOTH = "bad_mask_both.pkl"
  # TEST_IM_BAD_NAN = "bad_mask_nan.pkl"
  # TEST_IM_BAD_INF = "bad_mask_inf.pkl"
  PIXEL_VALUES = "pixel_inspector_rgb_values.txt"
  
  
  # ##########################
  # Tests for the main package
  # ##########################
  @pytest.mark.parametrize("debug", ["print", "plot"])
  def test_plantcv_debug(debug, tmpdir):
      from plantcv.plantcv._debug import _debug
      # Create a test tmp directory
      img_outdir = tmpdir.mkdir("sub")
      pcv.params.debug = debug
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      _debug(visual=img, filename=os.path.join(img_outdir, TEST_INPUT_COLOR))
      assert True
  
  
  @pytest.mark.parametrize("datatype,value", [[list, []], [int, 2], [float, 2.2], [bool, True], [str, "2"], [dict, {}],
                                              [tuple, ()], [None, None]])
  def test_plantcv_outputs_add_observation(datatype, value):
      # Create output instance
      outputs = pcv.Outputs()
      outputs.add_observation(sample='default', variable='test', trait='test variable', method='type', scale='none',
                              datatype=datatype, value=value, label=[])
      assert outputs.observations["default"]["test"]["value"] == value
  
  
  def test_plantcv_outputs_add_observation_invalid_type():
      # Create output instance
      outputs = pcv.Outputs()
      with pytest.raises(RuntimeError):
          outputs.add_observation(sample='default', variable='test', trait='test variable', method='type', scale='none',
                                  datatype=list, value=np.array([2]), label=[])
  
  def test_plantcv_outputs_save_results_json_newfile(tmpdir):
      # Create a test tmp directory
      cache_dir = tmpdir.mkdir("sub")
      outfile = os.path.join(cache_dir, "results.json")
      # Create output instance
      outputs = pcv.Outputs()
      outputs.add_observation(sample='default', variable='test', trait='test variable', method='test', scale='none',
                              datatype=str, value="test", label="none")
      outputs.save_results(filename=outfile, outformat="json")
      with open(outfile, "r") as fp:
          results = json.load(fp)
          assert results["observations"]["default"]["test"]["value"] == "test"
  
  
  def test_plantcv_outputs_save_results_json_existing_file(tmpdir):
      # Create a test tmp directory
      cache_dir = tmpdir.mkdir("sub")
      outfile = os.path.join(cache_dir, "data_results.txt")
      shutil.copyfile(os.path.join(TEST_DATA, "data_results.txt"), outfile)
      # Create output instance
      outputs = pcv.Outputs()
      outputs.add_observation(sample='default', variable='test', trait='test variable', method='test', scale='none',
                              datatype=str, value="test", label="none")
      outputs.save_results(filename=outfile, outformat="json")
      with open(outfile, "r") as fp:
          results = json.load(fp)
          assert results["observations"]["default"]["test"]["value"] == "test"
  
  
  def test_plantcv_outputs_save_results_csv(tmpdir):
      # Create a test tmp directory
      cache_dir = tmpdir.mkdir("sub")
      outfile = os.path.join(cache_dir, "results.csv")
      testfile = os.path.join(TEST_DATA, "data_results.csv")
      # Create output instance
      outputs = pcv.Outputs()
      outputs.add_observation(sample='default', variable='string', trait='string variable', method='string', scale='none',
                              datatype=str, value="string", label="none")
      outputs.add_observation(sample='default', variable='boolean', trait='boolean variable', method='boolean',
                              scale='none', datatype=bool, value=True, label="none")
      outputs.add_observation(sample='default', variable='list', trait='list variable', method='list',
                              scale='none', datatype=list, value=[1, 2, 3], label=[1, 2, 3])
      outputs.add_observation(sample='default', variable='tuple', trait='tuple variable', method='tuple',
                              scale='none', datatype=tuple, value=(1, 2), label=(1, 2))
      outputs.add_observation(sample='default', variable='tuple_list', trait='list of tuples variable',
                              method='tuple_list', scale='none', datatype=list, value=[(1, 2), (3, 4)], label=[1, 2])
      outputs.save_results(filename=outfile, outformat="csv")
      with open(outfile, "r") as fp:
          results = fp.read()
      with open(testfile, "r") as fp:
          test_results = fp.read()
      assert results == test_results
  
  def test_plantcv_acute():
      # Read in test data
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_MASK_SMALL), -1)
      contours_npz = np.load(os.path.join(TEST_DATA, TEST_VIS_COMP_CONTOUR), encoding="latin1")
      obj_contour = contours_npz['arr_0']
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.acute(obj=obj_contour, win=5, thresh=15, mask=mask)
      _ = pcv.acute(obj=obj_contour, win=0, thresh=15, mask=mask)
      _ = pcv.acute(obj=np.array(([[213, 190]], [[83, 61]], [[149, 246]])), win=84, thresh=192, mask=mask)
      _ = pcv.acute(obj=np.array(([[3, 29]], [[31, 102]], [[161, 63]])), win=148, thresh=56, mask=mask)
      _ = pcv.acute(obj=np.array(([[103, 154]], [[27, 227]], [[152, 83]])), win=35, thresh=0, mask=mask)
      # Test with debug = None
      pcv.params.debug = None
      _ = pcv.acute(obj=np.array(([[103, 154]], [[27, 227]], [[152, 83]])), win=35, thresh=0, mask=mask)
      _ = pcv.acute(obj=obj_contour, win=0, thresh=15, mask=mask)
      homology_pts = pcv.acute(obj=obj_contour, win=5, thresh=15, mask=mask)
      assert all([i == j] for i, j in zip(np.shape(homology_pts), (29, 1, 2)))
  
  
  def test_plantcv_acute_vertex():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_acute_vertex")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_VIS_SMALL))
      contours_npz = np.load(os.path.join(TEST_DATA, TEST_VIS_COMP_CONTOUR), encoding="latin1")
      obj_contour = contours_npz['arr_0']
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.acute_vertex(obj=obj_contour, win=5, thresh=15, sep=5, img=img, label="prefix")
      _ = pcv.acute_vertex(obj=[], win=5, thresh=15, sep=5, img=img)
      _ = pcv.acute_vertex(obj=[], win=.01, thresh=.01, sep=1, img=img)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.acute_vertex(obj=obj_contour, win=5, thresh=15, sep=5, img=img)
      # Test with debug = None
      pcv.params.debug = None
      acute = pcv.acute_vertex(obj=obj_contour, win=5, thresh=15, sep=5, img=img)
      assert all([i == j] for i, j in zip(np.shape(acute), np.shape(TEST_ACUTE_RESULT)))
      pcv.outputs.clear()
  
  
  def test_plantcv_acute_vertex_bad_obj():
      img = cv2.imread(os.path.join(TEST_DATA, TEST_VIS_SMALL))
      obj_contour = np.array([])
      pcv.params.debug = None
      result = pcv.acute_vertex(obj=obj_contour, win=5, thresh=15, sep=5, img=img)
      assert all([i == j] for i, j in zip(result, [0, ("NA", "NA")]))
      pcv.outputs.clear()
  
  
  def test_plantcv_analyze_bound_horizontal():
      # Clear previous outputs
      pcv.outputs.clear()
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_bound_horizontal")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      img_above_bound_only = cv2.imread(os.path.join(TEST_DATA, TEST_MASK_SMALL_PLANT))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1")
      object_contours = contours_npz['arr_0']
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.analyze_bound_horizontal(img=img, obj=object_contours, mask=mask, line_position=300, label="prefix")
      pcv.outputs.clear()
      _ = pcv.analyze_bound_horizontal(img=img, obj=object_contours, mask=mask, line_position=100)
      _ = pcv.analyze_bound_horizontal(img=img_above_bound_only, obj=object_contours, mask=mask, line_position=1756)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.analyze_bound_horizontal(img=img, obj=object_contours, mask=mask, line_position=1756)
      # Test with debug = None
      pcv.params.debug = None
      _ = pcv.analyze_bound_horizontal(img=img, obj=object_contours, mask=mask, line_position=1756)
      assert len(pcv.outputs.observations["default"]) == 7
  
  
  def test_plantcv_analyze_bound_horizontal_grayscale_image():
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1")
      object_contours = contours_npz['arr_0']
      # Test with a grayscale reference image and debug="plot"
      pcv.params.debug = "plot"
      boundary_img1 = pcv.analyze_bound_horizontal(img=img, obj=object_contours, mask=mask, line_position=1756)
      assert len(np.shape(boundary_img1)) == 3
  
  
  def test_plantcv_analyze_bound_horizontal_neg_y():
      # Clear previous outputs
      pcv.outputs.clear()
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_bound_horizontal")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1")
      object_contours = contours_npz['arr_0']
      # Test with debug=None, line position that will trigger -y
      pcv.params.debug = "plot"
      _ = pcv.analyze_bound_horizontal(img=img, obj=object_contours, mask=mask, line_position=-1000)
      _ = pcv.analyze_bound_horizontal(img=img, obj=object_contours, mask=mask, line_position=0)
      _ = pcv.analyze_bound_horizontal(img=img, obj=object_contours, mask=mask, line_position=2056)
      assert pcv.outputs.observations['default']['height_above_reference']['value'] == 713
  
  
  def test_plantcv_analyze_bound_vertical():
      # Clear previous outputs
      pcv.outputs.clear()
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_bound_vertical")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1")
      object_contours = contours_npz['arr_0']
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.analyze_bound_vertical(img=img, obj=object_contours, mask=mask, line_position=1000, label="prefix")
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.analyze_bound_vertical(img=img, obj=object_contours, mask=mask, line_position=1000)
      # Test with debug = None
      pcv.params.debug = None
      _ = pcv.analyze_bound_vertical(img=img, obj=object_contours, mask=mask, line_position=1000)
      assert pcv.outputs.observations['default']['width_left_reference']['value'] == 94
  
  
  def test_plantcv_analyze_bound_vertical_grayscale_image():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_bound_vertical")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1")
      object_contours = contours_npz['arr_0']
      # Test with a grayscale reference image and debug="plot"
      pcv.params.debug = "plot"
      _ = pcv.analyze_bound_vertical(img=img, obj=object_contours, mask=mask, line_position=1000)
      assert pcv.outputs.observations['default']['width_left_reference']['value'] == 94
      pcv.outputs.clear()
  
  
  def test_plantcv_analyze_bound_vertical_neg_x():
      # Clear previous outputs
      pcv.outputs.clear()
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_bound_vertical")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1")
      object_contours = contours_npz['arr_0']
      # Test with debug="plot", line position that will trigger -x
      pcv.params.debug = "plot"
      _ = pcv.analyze_bound_vertical(img=img, obj=object_contours, mask=mask, line_position=2454)
      assert pcv.outputs.observations['default']['width_left_reference']['value'] == 441
  
  
  def test_plantcv_analyze_bound_vertical_small_x():
      # Clear previous outputs
      pcv.outputs.clear()
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_bound_vertical")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1")
      object_contours = contours_npz['arr_0']
      # Test with debug='plot', line position that will trigger -x, and two channel object
      pcv.params.debug = "plot"
      _ = pcv.analyze_bound_vertical(img=img, obj=object_contours, mask=mask, line_position=1)
      assert pcv.outputs.observations['default']['width_right_reference']['value'] == 441
  
  
  def test_plantcv_analyze_color():
      # Clear previous outputs
      pcv.outputs.clear()
      # Test with debug = None
      pcv.params.debug = None
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type="all")
      _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type=None, label="prefix")
      _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type=None)
      _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type='lab')
      _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type='hsv')
      _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type=None)
  
      # Test with debug = "print"
      # pcv.params.debug = "print"
      _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type="all")
      _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type=None, label="prefix")
  
      # Test with debug = "plot"
      # pcv.params.debug = "plot"
      # _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type=None)
      _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type='lab')
      _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type='hsv')
      # _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type=None)
  
      # Test with debug = None
      # pcv.params.debug = None
      _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type='rgb')
      assert pcv.outputs.observations['default']['hue_median']['value'] == 84.0
  
  def test_plantcv_analyze_color_incorrect_image():
      img_binary = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      with pytest.raises(RuntimeError):
          _ = pcv.analyze_color(rgb_img=img_binary, mask=mask, hist_plot_type=None)
  #
  #
  def test_plantcv_analyze_color_bad_hist_type():
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      pcv.params.debug = "plot"
      with pytest.raises(RuntimeError):
          _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type='bgr')
  
  
  def test_plantcv_analyze_color_incorrect_hist_plot_type():
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      with pytest.raises(RuntimeError):
          pcv.params.debug = "plot"
          _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type="bgr")
  
  
  def test_plantcv_analyze_nir():
      # Clear previous outputs
      pcv.outputs.clear()
      # Test with debug=None
      pcv.params.debug = None
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), 0)
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
  
      _ = pcv.analyze_nir_intensity(gray_img=img, mask=mask, bins=256, histplot=True)
      result = len(pcv.outputs.observations['default']['nir_frequencies']['value'])
      assert result == 256
  
  
  def test_plantcv_analyze_nir_16bit():
      # Clear previous outputs
      pcv.outputs.clear()
      # Test with debug=None
      pcv.params.debug = None
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), 0)
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
  
      _ = pcv.analyze_nir_intensity(gray_img=np.uint16(img), mask=mask, bins=256, histplot=True)
      result = len(pcv.outputs.observations['default']['nir_frequencies']['value'])
      assert result == 256
  
  
  def test_plantcv_analyze_object():
      # Test with debug = None
      pcv.params.debug = None
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1")
      obj_contour = contours_npz['arr_0']
      obj_images = pcv.analyze_object(img=img, obj=obj_contour, mask=mask)
      pcv.outputs.clear()
      assert len(obj_images) != 0
  
  
  def test_plantcv_analyze_object_grayscale_input():
      # Test with debug = None
      pcv.params.debug = None
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), 0)
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1")
      obj_contour = contours_npz['arr_0']
      obj_images = pcv.analyze_object(img=img, obj=obj_contour, mask=mask)
      assert len(obj_images) != 1
  
  
  def test_plantcv_analyze_object_zero_slope():
      # Test with debug = None
      pcv.params.debug = None
      # Create a test image
      img = np.zeros((50, 50, 3), dtype=np.uint8)
      img[10:11, 10:40, 0] = 255
      mask = img[:, :, 0]
      obj_contour = np.array([[[10, 10]], [[11, 10]], [[12, 10]], [[13, 10]], [[14, 10]], [[15, 10]], [[16, 10]],
                              [[17, 10]], [[18, 10]], [[19, 10]], [[20, 10]], [[21, 10]], [[22, 10]], [[23, 10]],
                              [[24, 10]], [[25, 10]], [[26, 10]], [[27, 10]], [[28, 10]], [[29, 10]], [[30, 10]],
                              [[31, 10]], [[32, 10]], [[33, 10]], [[34, 10]], [[35, 10]], [[36, 10]], [[37, 10]],
                              [[38, 10]], [[39, 10]], [[38, 10]], [[37, 10]], [[36, 10]], [[35, 10]], [[34, 10]],
                              [[33, 10]], [[32, 10]], [[31, 10]], [[30, 10]], [[29, 10]], [[28, 10]], [[27, 10]],
                              [[26, 10]], [[25, 10]], [[24, 10]], [[23, 10]], [[22, 10]], [[21, 10]], [[20, 10]],
                              [[19, 10]], [[18, 10]], [[17, 10]], [[16, 10]], [[15, 10]], [[14, 10]], [[13, 10]],
                              [[12, 10]], [[11, 10]]], dtype=np.int32)
      obj_images = pcv.analyze_object(img=img, obj=obj_contour, mask=mask)
      assert len(obj_images) != 0
  
  
  def test_plantcv_analyze_object_longest_axis_2d():
      # Test with debug = None
      pcv.params.debug = None
      # Create a test image
      img = np.zeros((50, 50, 3), dtype=np.uint8)
      img[0:5, 45:49, 0] = 255
      img[0:5, 0:5, 0] = 255
      mask = img[:, :, 0]
      obj_contour = np.array([[[45, 1]], [[45, 2]], [[45, 3]], [[45, 4]], [[46, 4]], [[47, 4]], [[48, 4]],
                              [[48, 3]], [[48, 2]], [[48, 1]], [[47, 1]], [[46, 1]], [[1, 1]], [[1, 2]],
                              [[1, 3]], [[1, 4]], [[2, 4]], [[3, 4]], [[4, 4]], [[4, 3]], [[4, 2]],
                              [[4, 1]], [[3, 1]], [[2, 1]]], dtype=np.int32)
      obj_images = pcv.analyze_object(img=img, obj=obj_contour, mask=mask)
      assert len(obj_images) != 0
  
  
  def test_plantcv_analyze_object_longest_axis_2e():
      # Test with debug = None
      pcv.params.debug = None
      # Create a test image
      img = np.zeros((50, 50, 3), dtype=np.uint8)
      img[10:15, 10:40, 0] = 255
      mask = img[:, :, 0]
      obj_contour = np.array([[[10, 10]], [[10, 11]], [[10, 12]], [[10, 13]], [[10, 14]], [[11, 14]], [[12, 14]],
                              [[13, 14]], [[14, 14]], [[15, 14]], [[16, 14]], [[17, 14]], [[18, 14]], [[19, 14]],
                              [[20, 14]], [[21, 14]], [[22, 14]], [[23, 14]], [[24, 14]], [[25, 14]], [[26, 14]],
                              [[27, 14]], [[28, 14]], [[29, 14]], [[30, 14]], [[31, 14]], [[32, 14]], [[33, 14]],
                              [[34, 14]], [[35, 14]], [[36, 14]], [[37, 14]], [[38, 14]], [[39, 14]], [[39, 13]],
                              [[39, 12]], [[39, 11]], [[39, 10]], [[38, 10]], [[37, 10]], [[36, 10]], [[35, 10]],
                              [[34, 10]], [[33, 10]], [[32, 10]], [[31, 10]], [[30, 10]], [[29, 10]], [[28, 10]],
                              [[27, 10]], [[26, 10]], [[25, 10]], [[24, 10]], [[23, 10]], [[22, 10]], [[21, 10]],
                              [[20, 10]], [[19, 10]], [[18, 10]], [[17, 10]], [[16, 10]], [[15, 10]], [[14, 10]],
                              [[13, 10]], [[12, 10]], [[11, 10]]], dtype=np.int32)
      obj_images = pcv.analyze_object(img=img, obj=obj_contour, mask=mask)
      assert len(obj_images) != 0
  
  
  def test_plantcv_analyze_object_small_contour():
      # Test with debug = None
      pcv.params.debug = None
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      obj_contour = [np.array([[[0, 0]], [[0, 50]], [[50, 50]], [[50, 0]]], dtype=np.int32)]
      obj_images = pcv.analyze_object(img=img, obj=obj_contour, mask=mask)
      assert obj_images is None
  
  
  def test_plantcv_analyze_thermal_values():
      # Clear previous outputs
      pcv.outputs.clear()
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_thermal_values")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      # img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), 0)
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_THERMAL_IMG_MASK), -1)
      contours_npz = np.load(os.path.join(TEST_DATA, TEST_THERMAL_ARRAY), encoding="latin1")
      img = contours_npz['arr_0']
  
      pcv.params.debug = None
      thermal_hist = pcv.analyze_thermal_values(thermal_array=img, mask=mask, histplot=True)
      assert thermal_hist is not None and pcv.outputs.observations['default']['median_temp']['value'] == 33.20922
  
  def test_plantcv_apply_mask_white():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_apply_mask_white")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.apply_mask(img=img, mask=mask, mask_color="white")
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.apply_mask(img=img, mask=mask, mask_color="white")
      # Test with debug = None
      pcv.params.debug = None
      masked_img = pcv.apply_mask(img=img, mask=mask, mask_color="white")
      assert all([i == j] for i, j in zip(np.shape(masked_img), TEST_COLOR_DIM))
  
  
  def test_plantcv_apply_mask_black():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_apply_mask_black")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.apply_mask(img=img, mask=mask, mask_color="black")
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.apply_mask(img=img, mask=mask, mask_color="black")
      # Test with debug = None
      pcv.params.debug = None
      masked_img = pcv.apply_mask(img=img, mask=mask, mask_color="black")
      assert all([i == j] for i, j in zip(np.shape(masked_img), TEST_COLOR_DIM))
  
  
  def test_plantcv_apply_mask_hyperspectral():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_apply_mask_hyperspectral")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      hyper_array = pcv.hyperspectral.read_data(filename=spectral_filename)
  
      img = np.ones((2056, 2454))
      img_stacked = cv2.merge((img, img, img, img))
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.apply_mask(img=img_stacked, mask=img, mask_color="black")
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      masked_array = pcv.apply_mask(img=hyper_array.array_data, mask=img, mask_color="black")
      assert np.mean(masked_array) == 13.97111260224949
  
  
  def test_plantcv_apply_mask_bad_input():
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      with pytest.raises(RuntimeError):
          pcv.params.debug = "plot"
          _ = pcv.apply_mask(img=img, mask=mask, mask_color="wite")
  
  
  def test_plantcv_auto_crop():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_auto_crop")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), -1)
      contours = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_OBJECT), encoding="latin1")
      roi_contours = [contours[arr_n] for arr_n in contours]
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.auto_crop(img=img1, obj=roi_contours[1], padding_x=(20, 10), padding_y=(20, 10), color='black')
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.auto_crop(img=img1, obj=roi_contours[1], color='image')
      _ = pcv.auto_crop(img=img1, obj=roi_contours[1], padding_x=2000, padding_y=2000, color='image')
      # Test with debug = None
      pcv.params.debug = None
      cropped = pcv.auto_crop(img=img1, obj=roi_contours[1], padding_x=20, padding_y=20, color='black')
      x, y, z = np.shape(img1)
      x1, y1, z1 = np.shape(cropped)
      assert x > x1
  
  
  def test_plantcv_auto_crop_grayscale_input():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_auto_crop_grayscale_input")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), -1)
      gray_img = cv2.cvtColor(rgb_img, cv2.COLOR_BGR2GRAY)
      contours = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_OBJECT), encoding="latin1")
      roi_contours = [contours[arr_n] for arr_n in contours]
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      cropped = pcv.auto_crop(img=gray_img, obj=roi_contours[1], padding_x=20, padding_y=20, color='white')
      x, y = np.shape(gray_img)
      x1, y1 = np.shape(cropped)
      assert x > x1
  
  
  def test_plantcv_auto_crop_bad_color_input():
      # Read in test data
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), -1)
      gray_img = cv2.cvtColor(rgb_img, cv2.COLOR_BGR2GRAY)
      contours = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_OBJECT), encoding="latin1")
      roi_contours = [contours[arr_n] for arr_n in contours]
      with pytest.raises(RuntimeError):
          _ = pcv.auto_crop(img=gray_img, obj=roi_contours[1], padding_x=20, padding_y=20, color='wite')
  
  
  def test_plantcv_auto_crop_bad_padding_input():
      # Read in test data
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), -1)
      gray_img = cv2.cvtColor(rgb_img, cv2.COLOR_BGR2GRAY)
      contours = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_OBJECT), encoding="latin1")
      roi_contours = [contours[arr_n] for arr_n in contours]
      with pytest.raises(RuntimeError):
          _ = pcv.auto_crop(img=gray_img, obj=roi_contours[1], padding_x="one", padding_y=20, color='white')
  
  
  def test_plantcv_canny_edge_detect():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_canny_edge_detect")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.canny_edge_detect(img=rgb_img, mask=mask, mask_color='white')
      _ = pcv.canny_edge_detect(img=img, mask=mask, mask_color='black')
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.canny_edge_detect(img=img, thickness=2)
      _ = pcv.canny_edge_detect(img=img)
      # Test with debug = None
      pcv.params.debug = None
      edge_img = pcv.canny_edge_detect(img=img)
      # Assert that the output image has the dimensions of the input image
      if all([i == j] for i, j in zip(np.shape(edge_img), TEST_BINARY_DIM)):
          # Assert that the image is binary
          if all([i == j] for i, j in zip(np.unique(edge_img), [0, 255])):
              assert 1
          else:
              assert 0
      else:
          assert 0
  
  
  def test_plantcv_canny_edge_detect_bad_input():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_canny_edge_detect")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      with pytest.raises(RuntimeError):
          _ = pcv.canny_edge_detect(img=img, mask=mask, mask_color="gray")
  
  
  def test_plantcv_closing():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_closing")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), -1)
      gray_img = cv2.cvtColor(rgb_img, cv2.COLOR_BGR2GRAY)
      bin_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      # Test with debug=None
      pcv.params.debug = None
      _ = pcv.closing(gray_img)
      # Test with debug='plot'
      pcv.params.debug = 'plot'
      _ = pcv.closing(bin_img, np.ones((4, 4), np.uint8))
      # Test with debug='print'
      pcv.params.debug = 'print'
      filtered_img = pcv.closing(bin_img)
      assert np.sum(filtered_img) == 16261860
  
  
  def test_plantcv_closing_bad_input():
      # Read in test data
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), -1)
      with pytest.raises(RuntimeError):
          _ = pcv.closing(rgb_img)
  
  
  def test_plantcv_cluster_contours():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_cluster_contours")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), -1)
      roi_objects = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_OBJECT), encoding="latin1")
      hierarchy = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_HIERARCHY), encoding="latin1")
      objs = [roi_objects[arr_n] for arr_n in roi_objects]
      obj_hierarchy = hierarchy['arr_0']
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.cluster_contours(img=img1, roi_objects=objs, roi_obj_hierarchy=obj_hierarchy, nrow=4, ncol=6)
      _ = pcv.cluster_contours(img=img1, roi_objects=objs, roi_obj_hierarchy=obj_hierarchy, show_grid=True)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.cluster_contours(img=img1, roi_objects=objs, roi_obj_hierarchy=obj_hierarchy, nrow=4, ncol=6)
      # Test with debug = None
      pcv.params.debug = None
      clusters_i, contours, hierarchy = pcv.cluster_contours(img=img1, roi_objects=objs, roi_obj_hierarchy=obj_hierarchy,
                                                             nrow=4, ncol=6)
      lenori = len(objs)
      lenclust = len(clusters_i)
      assert lenori > lenclust
  
  
  def test_plantcv_cluster_contours_grayscale_input():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_cluster_contours_grayscale_input")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), 0)
      roi_objects = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_OBJECT), encoding="latin1")
      hierachy = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_HIERARCHY), encoding="latin1")
      objs = [roi_objects[arr_n] for arr_n in roi_objects]
      obj_hierarchy = hierachy['arr_0']
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.cluster_contours(img=img1, roi_objects=objs, roi_obj_hierarchy=obj_hierarchy, nrow=4, ncol=6)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.cluster_contours(img=img1, roi_objects=objs, roi_obj_hierarchy=obj_hierarchy, nrow=4, ncol=6)
      # Test with debug = None
      pcv.params.debug = None
      clusters_i, contours, hierachy = pcv.cluster_contours(img=img1, roi_objects=objs, roi_obj_hierarchy=obj_hierarchy,
                                                            nrow=4, ncol=6)
      lenori = len(objs)
      lenclust = len(clusters_i)
      assert lenori > lenclust
  
  
  def test_plantcv_cluster_contours_splitimg():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_cluster_contours_splitimg")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), -1)
      contours = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_CONTOUR), encoding="latin1")
      clusters = np.load(os.path.join(TEST_DATA, TEST_INPUT_ClUSTER_CONTOUR), encoding="latin1")
      hierachy = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_HIERARCHY), encoding="latin1")
      cluster_names = os.path.join(TEST_DATA, TEST_INPUT_GENOTXT)
      cluster_names_too_many = os.path.join(TEST_DATA, TEST_INPUT_GENOTXT_TOO_MANY)
      roi_contours = [contours[arr_n] for arr_n in contours]
      cluster_contours = [clusters[arr_n] for arr_n in clusters]
      obj_hierarchy = hierachy['arr_0']
      # Test with debug = None
      pcv.params.debug = None
      _, _, _ = pcv.cluster_contour_splitimg(img=img1, grouped_contour_indexes=cluster_contours,
                                             contours=roi_contours,
                                             hierarchy=obj_hierarchy, outdir=cache_dir, file=None, filenames=None)
      _, _, _ = pcv.cluster_contour_splitimg(img=img1, grouped_contour_indexes=[[0]], contours=[],
                                             hierarchy=np.array([[[1, -1, -1, -1]]]))
      _, _, _ = pcv.cluster_contour_splitimg(img=img1, grouped_contour_indexes=cluster_contours,
                                             contours=roi_contours,
                                             hierarchy=obj_hierarchy, outdir=cache_dir, file='multi', filenames=None)
      _, _, _ = pcv.cluster_contour_splitimg(img=img1, grouped_contour_indexes=cluster_contours,
                                             contours=roi_contours,
                                             hierarchy=obj_hierarchy, outdir=None, file=None, filenames=cluster_names)
      _, _, _ = pcv.cluster_contour_splitimg(img=img1, grouped_contour_indexes=cluster_contours,
                                             contours=roi_contours,
                                             hierarchy=obj_hierarchy, outdir=None, file=None,
                                             filenames=cluster_names_too_many)
      output_path, imgs, masks = pcv.cluster_contour_splitimg(img=img1, grouped_contour_indexes=cluster_contours,
                                                              contours=roi_contours, hierarchy=obj_hierarchy, outdir=None,
                                                              file=None,
                                                              filenames=None)
      assert len(output_path) != 0
  
  
  def test_plantcv_cluster_contours_splitimg_grayscale():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_cluster_contours_splitimg_grayscale")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), 0)
      contours = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_CONTOUR), encoding="latin1")
      clusters = np.load(os.path.join(TEST_DATA, TEST_INPUT_ClUSTER_CONTOUR), encoding="latin1")
      hierachy = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_HIERARCHY), encoding="latin1")
      cluster_names = os.path.join(TEST_DATA, TEST_INPUT_GENOTXT)
      cluster_names_too_many = os.path.join(TEST_DATA, TEST_INPUT_GENOTXT_TOO_MANY)
      roi_contours = [contours[arr_n] for arr_n in contours]
      cluster_contours = [clusters[arr_n] for arr_n in clusters]
      obj_hierarchy = hierachy['arr_0']
      pcv.params.debug = None
      output_path, imgs, masks = pcv.cluster_contour_splitimg(img=img1, grouped_contour_indexes=cluster_contours,
                                                              contours=roi_contours, hierarchy=obj_hierarchy, outdir=None,
                                                              file=None,
                                                              filenames=None)
      assert len(output_path) != 0
  
  
  def test_plantcv_color_palette():
      # Return a color palette
      colors = pcv.color_palette(num=10, saved=False)
      assert np.shape(colors) == (10, 3)
  
  
  def test_plantcv_color_palette_random():
      # Return a color palette in random order
      pcv.params.color_sequence = "random"
      colors = pcv.color_palette(num=10, saved=False)
      assert np.shape(colors) == (10, 3)
  
  
  def test_plantcv_color_palette_saved():
      # Return a color palette that was saved
      pcv.params.saved_color_scale = [[0, 0, 0], [255, 255, 255]]
      colors = pcv.color_palette(num=2, saved=True)
      assert colors == [[0, 0, 0], [255, 255, 255]]
  
  
  def test_plantcv_crop():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_crop")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      img, _, _ = pcv.readimage(os.path.join(TEST_DATA, TEST_INPUT_NIR_MASK), 'gray')
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.crop(img=img, x=10, y=10, h=50, w=50)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      cropped = pcv.crop(img=img, x=10, y=10, h=50, w=50)
      assert np.shape(cropped) == (50, 50)
  
  
  def test_plantcv_crop_hyperspectral():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_crop_hyperspectral")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = np.ones((2056, 2454))
      img_stacked = cv2.merge((img, img, img, img))
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.crop(img=img_stacked, x=10, y=10, h=50, w=50)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      cropped = pcv.crop(img=img_stacked, x=10, y=10, h=50, w=50)
      assert np.shape(cropped) == (50, 50, 4)
  
  
  def test_plantcv_crop_position_mask():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_crop_position_mask")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      nir, path1, filename1 = pcv.readimage(os.path.join(TEST_DATA, TEST_INPUT_NIR_MASK), 'gray')
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK), -1)
      mask_three_channel = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK), -1)
      mask_resize = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK_RESIZE), -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.crop_position_mask(nir, mask, x=40, y=3, v_pos="top", h_pos="right")
      _ = pcv.crop_position_mask(nir, mask_resize, x=40, y=3, v_pos="top", h_pos="right")
      _ = pcv.crop_position_mask(nir, mask_three_channel, x=40, y=3, v_pos="top", h_pos="right")
      # Test with debug = "print" with bottom
      _ = pcv.crop_position_mask(nir, mask, x=40, y=3, v_pos="bottom", h_pos="left")
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.crop_position_mask(nir, mask, x=40, y=3, v_pos="top", h_pos="right")
      # Test with debug = "plot" with bottom
      _ = pcv.crop_position_mask(nir, mask, x=45, y=2, v_pos="bottom", h_pos="left")
      # Test with debug = None
      pcv.params.debug = None
      newmask = pcv.crop_position_mask(nir, mask, x=40, y=3, v_pos="top", h_pos="right")
      assert np.sum(newmask) == 707115
  
  
  def test_plantcv_crop_position_mask_color():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_crop_position_mask")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      nir, path1, filename1 = pcv.readimage(os.path.join(TEST_DATA, TEST_INPUT_COLOR), mode='native')
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK), -1)
      mask_resize = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK_RESIZE))
      mask_non_binary = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK))
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.crop_position_mask(nir, mask, x=40, y=3, v_pos="top", h_pos="right")
      # Test with debug = "print" with bottom
      _ = pcv.crop_position_mask(nir, mask, x=40, y=3, v_pos="bottom", h_pos="left")
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.crop_position_mask(nir, mask, x=40, y=3, v_pos="top", h_pos="right")
      # Test with debug = "plot" with bottom
      _ = pcv.crop_position_mask(nir, mask, x=45, y=2, v_pos="bottom", h_pos="left")
      _ = pcv.crop_position_mask(nir, mask_non_binary, x=45, y=2, v_pos="bottom", h_pos="left")
      _ = pcv.crop_position_mask(nir, mask_non_binary, x=45, y=2, v_pos="top", h_pos="left")
      _ = pcv.crop_position_mask(nir, mask_non_binary, x=45, y=2, v_pos="bottom", h_pos="right")
      _ = pcv.crop_position_mask(nir, mask_resize, x=45, y=2, v_pos="top", h_pos="left")
  
      # Test with debug = None
      pcv.params.debug = None
      newmask = pcv.crop_position_mask(nir, mask, x=40, y=3, v_pos="top", h_pos="right")
      assert np.sum(newmask) == 707115
  
  
  def test_plantcv_crop_position_mask_bad_input_x():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_crop_position_mask")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK), -1)
      # Read in test data
      nir, path1, filename1 = pcv.readimage(os.path.join(TEST_DATA, TEST_INPUT_NIR_MASK))
      pcv.params.debug = None
      with pytest.raises(RuntimeError):
          _ = pcv.crop_position_mask(nir, mask, x=-1, y=-1, v_pos="top", h_pos="right")
  
  
  def test_plantcv_crop_position_mask_bad_input_vpos():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_crop_position_mask")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK), -1)
      # Read in test data
      nir, path1, filename1 = pcv.readimage(os.path.join(TEST_DATA, TEST_INPUT_NIR_MASK))
      pcv.params.debug = None
      with pytest.raises(RuntimeError):
          _ = pcv.crop_position_mask(nir, mask, x=40, y=3, v_pos="below", h_pos="right")
  
  
  def test_plantcv_crop_position_mask_bad_input_hpos():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_crop_position_mask")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK), -1)
      # Read in test data
      nir, path1, filename1 = pcv.readimage(os.path.join(TEST_DATA, TEST_INPUT_NIR_MASK))
      pcv.params.debug = None
      with pytest.raises(RuntimeError):
          _ = pcv.crop_position_mask(nir, mask, x=40, y=3, v_pos="top", h_pos="starboard")
  
  
  def test_plantcv_dilate():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_dilate")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.dilate(gray_img=img, ksize=5, i=1)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.dilate(gray_img=img, ksize=5, i=1)
      # Test with debug = None
      pcv.params.debug = None
      dilate_img = pcv.dilate(gray_img=img, ksize=5, i=1)
      # Assert that the output image has the dimensions of the input image
      if all([i == j] for i, j in zip(np.shape(dilate_img), TEST_BINARY_DIM)):
          # Assert that the image is binary
          if all([i == j] for i, j in zip(np.unique(dilate_img), [0, 255])):
              assert 1
          else:
              assert 0
      else:
          assert 0
  
  
  def test_plantcv_dilate_small_k():
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      # Test with debug = None
      pcv.params.debug = None
      with pytest.raises(ValueError):
          _ = pcv.dilate(img, 1, 1)
  
  
  def test_plantcv_erode():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_erode")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.erode(gray_img=img, ksize=5, i=1)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.erode(gray_img=img, ksize=5, i=1)
      # Test with debug = None
      pcv.params.debug = None
      erode_img = pcv.erode(gray_img=img, ksize=5, i=1)
      # Assert that the output image has the dimensions of the input image
      if all([i == j] for i, j in zip(np.shape(erode_img), TEST_BINARY_DIM)):
          # Assert that the image is binary
          if all([i == j] for i, j in zip(np.unique(erode_img), [0, 255])):
              assert 1
          else:
              assert 0
      else:
          assert 0
  
  
  def test_plantcv_erode_small_k():
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      # Test with debug = None
      pcv.params.debug = None
      with pytest.raises(ValueError):
          _ = pcv.erode(img, 1, 1)
  
  
  def test_plantcv_distance_transform():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_distance_transform")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_CROPPED_MASK), -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.distance_transform(bin_img=mask, distance_type=1, mask_size=3)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.distance_transform(bin_img=mask, distance_type=1, mask_size=3)
      # Test with debug = None
      pcv.params.debug = None
      distance_transform_img = pcv.distance_transform(bin_img=mask, distance_type=1, mask_size=3)
      # Assert that the output image has the dimensions of the input image
      assert all([i == j] for i, j in zip(np.shape(distance_transform_img), np.shape(mask)))
  
  
  def test_plantcv_fatal_error():
      # Verify that the fatal_error function raises a RuntimeError
      with pytest.raises(RuntimeError):
          pcv.fatal_error("Test error")
  
  
  def test_plantcv_fill():
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      # Test with debug = None
      pcv.params.debug = None
      fill_img = pcv.fill(bin_img=img, size=63632)
      # Assert that the output image has the dimensions of the input image
      # assert all([i == j] for i, j in zip(np.shape(fill_img), TEST_BINARY_DIM))
      assert np.sum(fill_img) == 0
  
  
  def test_plantcv_fill_bad_input():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_fill_bad_input")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      with pytest.raises(RuntimeError):
          _ = pcv.fill(bin_img=img, size=1)
  
  
  def test_plantcv_fill_holes():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_fill_holes")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.fill_holes(bin_img=img)
      pcv.params.debug = "plot"
      _ = pcv.fill_holes(bin_img=img)
      # Test with debug = None
      pcv.params.debug = None
      fill_img = pcv.fill_holes(bin_img=img)
      assert np.sum(fill_img) > np.sum(img)
  
  
  def test_plantcv_fill_holes_bad_input():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_fill_holes_bad_input")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      with pytest.raises(RuntimeError):
          _ = pcv.fill_holes(bin_img=img)
  
  
  def test_plantcv_find_objects():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_find_objects")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.find_objects(img=img, mask=mask)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.find_objects(img=img, mask=mask)
      # Test with debug = None
      pcv.params.debug = None
      contours, hierarchy = pcv.find_objects(img=img, mask=mask)
      # Assert the correct number of contours are found
      assert len(contours) == 2
  
  
  def test_plantcv_find_objects_grayscale_input():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_find_objects_grayscale_input")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), 0)
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      contours, hierarchy = pcv.find_objects(img=img, mask=mask)
      # Assert the correct number of contours are found
      assert len(contours) == 2
  
  
  def test_plantcv_flip():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_flip")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      img_binary = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.flip(img=img, direction="horizontal")
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.flip(img=img, direction="vertical")
      _ = pcv.flip(img=img_binary, direction="vertical")
      # Test with debug = None
      pcv.params.debug = None
      flipped_img = pcv.flip(img=img, direction="horizontal")
      assert all([i == j] for i, j in zip(np.shape(flipped_img), TEST_COLOR_DIM))
  
  
  def test_plantcv_flip_bad_input():
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      pcv.params.debug = None
      with pytest.raises(RuntimeError):
          _ = pcv.flip(img=img, direction="vert")
  
  
  def test_plantcv_gaussian_blur():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_gaussian_blur")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      img_color = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.gaussian_blur(img=img, ksize=(51, 51), sigma_x=0, sigma_y=None)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.gaussian_blur(img=img, ksize=(51, 51), sigma_x=0, sigma_y=None)
      _ = pcv.gaussian_blur(img=img_color, ksize=(51, 51), sigma_x=0, sigma_y=None)
      # Test with debug = None
      pcv.params.debug = None
      gaussian_img = pcv.gaussian_blur(img=img, ksize=(51, 51), sigma_x=0, sigma_y=None)
      imgavg = np.average(img)
      gavg = np.average(gaussian_img)
      assert gavg != imgavg
  
  
  def test_plantcv_get_kernel_cross():
      kernel = pcv.get_kernel(size=(3, 3), shape="cross")
      assert (kernel == np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]])).all()
  
  
  def test_plantcv_get_kernel_rectangle():
      kernel = pcv.get_kernel(size=(3, 3), shape="rectangle")
      assert (kernel == np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]])).all()
  
  
  def test_plantcv_get_kernel_ellipse():
      kernel = pcv.get_kernel(size=(3, 3), shape="ellipse")
      assert (kernel == np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]])).all()
  
  
  def test_plantcv_get_kernel_bad_input_size():
      with pytest.raises(ValueError):
          _ = pcv.get_kernel(size=(1, 1), shape="ellipse")
  
  
  def test_plantcv_get_kernel_bad_input_shape():
      with pytest.raises(RuntimeError):
          _ = pcv.get_kernel(size=(3, 1), shape="square")
  
  
  def test_plantcv_get_nir_sv():
      nirpath = pcv.get_nir(TEST_DATA, TEST_VIS)
      nirpath1 = os.path.join(TEST_DATA, TEST_NIR)
      assert nirpath == nirpath1
  
  
  def test_plantcv_get_nir_tv():
      nirpath = pcv.get_nir(TEST_DATA, TEST_VIS_TV)
      nirpath1 = os.path.join(TEST_DATA, TEST_NIR_TV)
      assert nirpath == nirpath1
  
  
  def test_plantcv_hist_equalization():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hist_equalization")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.hist_equalization(gray_img=img)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.hist_equalization(gray_img=img)
      # Test with debug = None
      pcv.params.debug = None
      hist = pcv.hist_equalization(gray_img=img)
      histavg = np.average(hist)
      imgavg = np.average(img)
      assert histavg != imgavg
  
  
  def test_plantcv_hist_equalization_bad_input():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hist_equalization_bad_input")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), 1)
      # Test with debug = None
      pcv.params.debug = None
      with pytest.raises(RuntimeError):
          _ = pcv.hist_equalization(gray_img=img)
  
  
  def test_plantcv_image_add():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_image_add")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      img2 = np.copy(img1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.image_add(gray_img1=img1, gray_img2=img2)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.image_add(gray_img1=img1, gray_img2=img2)
      # Test with debug = None
      pcv.params.debug = None
      added_img = pcv.image_add(gray_img1=img1, gray_img2=img2)
      assert all([i == j] for i, j in zip(np.shape(added_img), TEST_BINARY_DIM))
  
  
  def test_plantcv_image_fusion():
      # Read in test data
      # 16-bit image
      img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMAX), -1)
      img2 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMIN))
      # 8-bit image
      img2 = img_as_ubyte(img2)
      fused_img = pcv.image_fusion(img1, img2, [480.0], [550.0, 640.0, 800.0])
      assert str(type(fused_img)) == "<class 'plantcv.plantcv.classes.Spectral_data'>"
  
  
  def test_plantcv_image_fusion_size_diff():
      img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), 0)
      img2 = np.copy(img1)
      img2 = img2[0:10, 0:10]
      with pytest.raises(RuntimeError):
          _ = pcv.image_fusion(img1, img2, [480.0, 550.0, 670.0], [480.0, 550.0, 670.0])
  
  
  def test_plantcv_image_subtract():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_image_sub")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # read in images
      img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      img2 = np.copy(img1)
      # Test with debug = "print"
      pcv.params.debug = 'print'
      _ = pcv.image_subtract(img1, img2)
      # Test with debug = "plot"
      pcv.params.debug = 'plot'
      _ = pcv.image_subtract(img1, img2)
      # Test with debug = None
      pcv.params.debug = None
      new_img = pcv.image_subtract(img1, img2)
      assert np.array_equal(new_img, np.zeros(np.shape(new_img), np.uint8))
  
  def test_plantcv_image_subtract_fail():
      # read in images
      img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      img2 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY))
      # test
      with pytest.raises(RuntimeError):
          _ = pcv.image_subtract(img1, img2)
  
  
  def test_plantcv_invert():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_invert")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.invert(gray_img=img)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.invert(gray_img=img)
      # Test with debug = None
      pcv.params.debug = None
      inverted_img = pcv.invert(gray_img=img)
      # Assert that the output image has the dimensions of the input image
      if all([i == j] for i, j in zip(np.shape(inverted_img), TEST_BINARY_DIM)):
          # Assert that the image is binary
          if all([i == j] for i, j in zip(np.unique(inverted_img), [0, 255])):
              assert 1
          else:
              assert 0
      else:
          assert 0
  
  
  def test_plantcv_landmark_reference_pt_dist():
      # Clear previous outputs
      pcv.outputs.clear()
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_landmark_reference")
      os.mkdir(cache_dir)
      points_rescaled = [(0.0139, 0.2569), (0.2361, 0.2917), (0.3542, 0.3819), (0.3542, 0.4167), (0.375, 0.4236),
                         (0.7431, 0.3681), (0.8958, 0.3542), (0.9931, 0.3125), (0.1667, 0.5139), (0.4583, 0.8889),
                         (0.4931, 0.5903), (0.3889, 0.5694), (0.4792, 0.4306), (0.2083, 0.5417), (0.3194, 0.5278),
                         (0.3889, 0.375), (0.3681, 0.3472), (0.2361, 0.0139), (0.5417, 0.2292), (0.7708, 0.3472),
                         (0.6458, 0.3472), (0.6389, 0.5208), (0.6458, 0.625)]
      centroid_rescaled = (0.4685, 0.4945)
      bottomline_rescaled = (0.4685, 0.2569)
      _ = pcv.landmark_reference_pt_dist(points_r=[], centroid_r=('a', 'b'), bline_r=(0, 0))
      _ = pcv.landmark_reference_pt_dist(points_r=[(10, 1000)], centroid_r=(10, 10), bline_r=(10, 10))
      _ = pcv.landmark_reference_pt_dist(points_r=[], centroid_r=(0, 0), bline_r=(0, 0))
      _ = pcv.landmark_reference_pt_dist(points_r=points_rescaled, centroid_r=centroid_rescaled,
                                         bline_r=bottomline_rescaled, label="prefix")
      assert len(pcv.outputs.observations['prefix'].keys()) == 8
  
  
  def test_plantcv_laplace_filter():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_laplace_filter")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.laplace_filter(gray_img=img, ksize=1, scale=1)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.laplace_filter(gray_img=img, ksize=1, scale=1)
      # Test with debug = None
      pcv.params.debug = None
      lp_img = pcv.laplace_filter(gray_img=img, ksize=1, scale=1)
      # Assert that the output image has the dimensions of the input image
      assert all([i == j] for i, j in zip(np.shape(lp_img), TEST_GRAY_DIM))
  
  
  def test_plantcv_logical_and():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_logical_and")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      img2 = np.copy(img1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.logical_and(bin_img1=img1, bin_img2=img2)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.logical_and(bin_img1=img1, bin_img2=img2)
      # Test with debug = None
      pcv.params.debug = None
      and_img = pcv.logical_and(bin_img1=img1, bin_img2=img2)
      assert all([i == j] for i, j in zip(np.shape(and_img), TEST_BINARY_DIM))
  
  
  def test_plantcv_logical_or():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_logical_or")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      img2 = np.copy(img1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.logical_or(bin_img1=img1, bin_img2=img2)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.logical_or(bin_img1=img1, bin_img2=img2)
      # Test with debug = None
      pcv.params.debug = None
      or_img = pcv.logical_or(bin_img1=img1, bin_img2=img2)
      assert all([i == j] for i, j in zip(np.shape(or_img), TEST_BINARY_DIM))
  
  
  def test_plantcv_logical_xor():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_logical_xor")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      img2 = np.copy(img1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.logical_xor(bin_img1=img1, bin_img2=img2)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.logical_xor(bin_img1=img1, bin_img2=img2)
      # Test with debug = None
      pcv.params.debug = None
      xor_img = pcv.logical_xor(bin_img1=img1, bin_img2=img2)
      assert all([i == j] for i, j in zip(np.shape(xor_img), TEST_BINARY_DIM))
  
  
  def test_plantcv_median_blur():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_median_blur")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.median_blur(gray_img=img, ksize=5)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.median_blur(gray_img=img, ksize=5)
      # Test with debug = None
      pcv.params.debug = None
      blur_img = pcv.median_blur(gray_img=img, ksize=5)
      # Assert that the output image has the dimensions of the input image
      if all([i == j] for i, j in zip(np.shape(blur_img), TEST_BINARY_DIM)):
          # Assert that the image is binary
          if all([i == j] for i, j in zip(np.unique(blur_img), [0, 255])):
              assert 1
          else:
              assert 0
      else:
          assert 0
  
  
  def test_plantcv_median_blur_bad_input():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_median_blur_bad_input")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      with pytest.raises(RuntimeError):
          _ = pcv.median_blur(img, 5.)
  
  
  def test_plantcv_naive_bayes_classifier():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_naive_bayes_classifier")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.naive_bayes_classifier(rgb_img=img, pdf_file=os.path.join(TEST_DATA, TEST_PDFS))
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.naive_bayes_classifier(rgb_img=img, pdf_file=os.path.join(TEST_DATA, TEST_PDFS))
      # Test with debug = None
      pcv.params.debug = None
      mask = pcv.naive_bayes_classifier(rgb_img=img, pdf_file=os.path.join(TEST_DATA, TEST_PDFS))
  
      # Assert that the output image has the dimensions of the input image
      if all([i == j] for i, j in zip(np.shape(mask), TEST_GRAY_DIM)):
          # Assert that the image is binary
          if all([i == j] for i, j in zip(np.unique(mask), [0, 255])):
              assert 1
          else:
              assert 0
      else:
          assert 0
  
  
  def test_plantcv_naive_bayes_classifier_bad_input():
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      pcv.params.debug = None
      with pytest.raises(RuntimeError):
          _ = pcv.naive_bayes_classifier(rgb_img=img, pdf_file=os.path.join(TEST_DATA, TEST_PDFS_BAD))
  
  
  def test_plantcv_object_composition():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_object_composition")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      object_contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_OBJECT_CONTOURS), encoding="latin1")
      object_contours = [object_contours_npz[arr_n] for arr_n in object_contours_npz]
      object_hierarchy_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_OBJECT_HIERARCHY), encoding="latin1")
      object_hierarchy = object_hierarchy_npz['arr_0']
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.object_composition(img=img, contours=object_contours, hierarchy=object_hierarchy)
      _ = pcv.object_composition(img=img, contours=[], hierarchy=object_hierarchy)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.object_composition(img=img, contours=object_contours, hierarchy=object_hierarchy)
      # Test with debug = None
      pcv.params.debug = None
      contours, mask = pcv.object_composition(img=img, contours=object_contours, hierarchy=object_hierarchy)
      # Assert that the objects have been combined
      contour_shape = np.shape(contours)  # type: tuple
      assert contour_shape[1] == 1
  
  
  def test_plantcv_object_composition_grayscale_input():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_object_composition_grayscale_input")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), 0)
      object_contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_OBJECT_CONTOURS), encoding="latin1")
      object_contours = [object_contours_npz[arr_n] for arr_n in object_contours_npz]
      object_hierarchy_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_OBJECT_HIERARCHY), encoding="latin1")
      object_hierarchy = object_hierarchy_npz['arr_0']
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      contours, mask = pcv.object_composition(img=img, contours=object_contours, hierarchy=object_hierarchy)
      # Assert that the objects have been combined
      contour_shape = np.shape(contours)  # type: tuple
      assert contour_shape[1] == 1
  
  
  def test_plantcv_within_frame():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_within_frame")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      mask_ib = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK), -1)
      mask_oob = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK_OOB), -1)
      in_bounds_ib = pcv.within_frame(mask=mask_ib, border_width=1, label="prefix")
      in_bounds_oob = pcv.within_frame(mask=mask_oob, border_width=1)
      assert (in_bounds_ib is True and in_bounds_oob is False)
  
  
  def test_plantcv_within_frame_bad_input():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_within_frame")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      grayscale_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), 0)
      with pytest.raises(RuntimeError):
          _ = pcv.within_frame(grayscale_img)
  
  
  def test_plantcv_opening():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_closing")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), -1)
      gray_img = cv2.cvtColor(rgb_img, cv2.COLOR_BGR2GRAY)
      bin_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      # Test with debug=None
      pcv.params.debug = None
      _ = pcv.opening(gray_img)
      # Test with debug='plot'
      pcv.params.debug = 'plot'
      _ = pcv.opening(bin_img, np.ones((4, 4), np.uint8))
      # Test with debug='print'
      pcv.params.debug = 'print'
      filtered_img = pcv.opening(bin_img)
      assert np.sum(filtered_img) == 16184595
  
  
  def test_plantcv_opening_bad_input():
      # Read in test data
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), -1)
      with pytest.raises(RuntimeError):
          _ = pcv.opening(rgb_img)
  
  
  def test_plantcv_output_mask():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_output_mask")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      img_color = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), -1)
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.output_mask(img=img, mask=mask, filename='test.png', outdir=None, mask_only=False)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.output_mask(img=img, mask=mask, filename='test.png', outdir=cache_dir, mask_only=False)
      _ = pcv.output_mask(img=img_color, mask=mask, filename='test.png', outdir=None, mask_only=False)
      # Remove tmp files in working direcctory
      shutil.rmtree("ori-images")
      shutil.rmtree("mask-images")
      # Test with debug = None
      pcv.params.debug = None
      imgpath, maskpath, analysis_images = pcv.output_mask(img=img, mask=mask, filename='test.png',
                                                           outdir=cache_dir, mask_only=False)
      assert all([os.path.exists(imgpath) is True, os.path.exists(maskpath) is True])
  
  
  def test_plantcv_output_mask_true():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_output_mask")
      pcv.params.debug_outdir = cache_dir
      os.mkdir(cache_dir)
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      img_color = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), -1)
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.output_mask(img=img, mask=mask, filename='test.png', outdir=cache_dir, mask_only=True)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.output_mask(img=img_color, mask=mask, filename='test.png', outdir=cache_dir, mask_only=True)
      pcv.params.debug = None
      imgpath, maskpath, analysis_images = pcv.output_mask(img=img, mask=mask, filename='test.png', outdir=cache_dir,
                                                           mask_only=False)
      assert all([os.path.exists(imgpath) is True, os.path.exists(maskpath) is True])
  
  
  def test_plantcv_plot_image_matplotlib_input():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_pseudocolor")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      pimg = pcv.visualize.pseudocolor(gray_img=img, mask=mask, min_value=10, max_value=200)
      with pytest.raises(RuntimeError):
          pcv.plot_image(pimg)
  
  
  def test_plantcv_plot_image_plotnine():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_plot_image_plotnine")
      os.mkdir(cache_dir)
      dataset = pd.DataFrame({'x': [1, 2, 3, 4], 'y': [1, 2, 3, 4]})
      img = ggplot(data=dataset)
      try:
          pcv.plot_image(img=img)
      except RuntimeError:
          assert False
      # Assert that the image was plotted without error
      assert True
  
  
  def test_plantcv_print_image():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_print_image")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img, path, img_name = pcv.readimage(filename=os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      filename = os.path.join(cache_dir, 'plantcv_print_image.png')
      pcv.print_image(img=img, filename=filename)
      # Assert that the file was created
      assert os.path.exists(filename) is True
  
  
  def test_plantcv_print_image_bad_type():
      with pytest.raises(RuntimeError):
          pcv.print_image(img=[], filename="/dev/null")
  
  
  def test_plantcv_print_image_plotnine():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_print_image_plotnine")
      os.mkdir(cache_dir)
      dataset = pd.DataFrame({'x': [1, 2, 3, 4], 'y': [1, 2, 3, 4]})
      img = ggplot(data=dataset)
      filename = os.path.join(cache_dir, 'plantcv_print_image.png')
      pcv.print_image(img=img, filename=filename)
      # Assert that the file was created
      assert os.path.exists(filename) is True
  
  
  def test_plantcv_print_image_matplotlib():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_print_image_plotnine")
      os.mkdir(cache_dir)
      # Input data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      plt.figure()
      plt.imshow(img)
      plot = plt.gcf()
      filename = os.path.join(cache_dir, 'plantcv_print_image.png')
      pcv.print_image(img=plot, filename=filename)
      # Assert that the file was created
      assert os.path.exists(filename) is True
  
  
  def test_plantcv_print_results(tmpdir):
      # Create a tmp directory
      cache_dir = tmpdir.mkdir("sub")
      outfile = os.path.join(cache_dir, "results.json")
      pcv.print_results(filename=outfile)
      assert os.path.exists(outfile)
  
  
  def test_plantcv_readimage_native():
      # Test with debug = None
      pcv.params.debug = None
      _ = pcv.readimage(filename=os.path.join(TEST_DATA, TEST_INPUT_COLOR), mode='rgba')
      _ = pcv.readimage(filename=os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      img, path, img_name = pcv.readimage(filename=os.path.join(TEST_DATA, TEST_INPUT_COLOR), mode='native')
      # Assert that the image name returned equals the name of the input image
      # Assert that the path of the image returned equals the path of the input image
      # Assert that the dimensions of the returned image equals the expected dimensions
      if img_name == TEST_INPUT_COLOR and path == TEST_DATA:
          if all([i == j] for i, j in zip(np.shape(img), TEST_COLOR_DIM)):
              assert 1
          else:
              assert 0
      else:
          assert 0
  
  
  def test_plantcv_readimage_grayscale():
      # Test with debug = None
      pcv.params.debug = None
      _, _, _ = pcv.readimage(filename=os.path.join(TEST_DATA, TEST_INPUT_GRAY), mode="grey")
      img, path, img_name = pcv.readimage(filename=os.path.join(TEST_DATA, TEST_INPUT_GRAY), mode="gray")
      assert len(np.shape(img)) == 2
  
  
  def test_plantcv_readimage_rgb():
      # Test with debug = None
      pcv.params.debug = None
      img, path, img_name = pcv.readimage(filename=os.path.join(TEST_DATA, TEST_INPUT_GRAY), mode="rgb")
      assert len(np.shape(img)) == 3
  
  
  def test_plantcv_readimage_rgba_as_rgb():
      # Test with debug = None
      pcv.params.debug = None
      img, path, img_name = pcv.readimage(filename=os.path.join(TEST_DATA, TEST_INPUT_RGBA), mode="native")
      assert np.shape(img)[2] == 3
  
  
  def test_plantcv_readimage_csv():
      # Test with debug = None
      pcv.params.debug = None
      img, path, img_name = pcv.readimage(filename=os.path.join(TEST_DATA, TEST_INPUT_THERMAL_CSV), mode="csv")
      assert len(np.shape(img)) == 2
  
  
  def test_plantcv_readimage_envi():
      # Test with debug = None
      pcv.params.debug = None
      array_data = pcv.readimage(filename=os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA), mode="envi")
      if sys.version_info[0] < 3:
          assert len(array_data.array_type) == 8
  
  
  def test_plantcv_readimage_bad_file():
      with pytest.raises(RuntimeError):
          _ = pcv.readimage(filename=TEST_INPUT_COLOR)
  
  
  def test_plantcv_readbayer_default_bg():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_readbayer_default_bg")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Test with debug = "print"
      pcv.params.debug = "print"
      _, _, _ = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER),
                              bayerpattern="BG", alg="default")
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER),
                                          bayerpattern="BG", alg="default")
      assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3)))
  
  
  def test_plantcv_readbayer_default_gb():
      # Test with debug = None
      pcv.params.debug = None
      img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER),
                                          bayerpattern="GB", alg="default")
      assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3)))
  
  
  def test_plantcv_readbayer_default_rg():
      # Test with debug = None
      pcv.params.debug = None
      img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER),
                                          bayerpattern="RG", alg="default")
      assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3)))
  
  
  def test_plantcv_readbayer_default_gr():
      # Test with debug = None
      pcv.params.debug = None
      img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER),
                                          bayerpattern="GR", alg="default")
      assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3)))
  
  
  def test_plantcv_readbayer_edgeaware_bg():
      # Test with debug = None
      pcv.params.debug = None
      img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER),
                                          bayerpattern="BG", alg="edgeaware")
      assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3)))
  
  
  def test_plantcv_readbayer_edgeaware_gb():
      # Test with debug = None
      pcv.params.debug = None
      img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER),
                                          bayerpattern="GB", alg="edgeaware")
      assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3)))
  
  
  def test_plantcv_readbayer_edgeaware_rg():
      # Test with debug = None
      pcv.params.debug = None
      img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER),
                                          bayerpattern="RG", alg="edgeaware")
      assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3)))
  
  
  def test_plantcv_readbayer_edgeaware_gr():
      # Test with debug = None
      pcv.params.debug = None
      img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER),
                                          bayerpattern="GR", alg="edgeaware")
      assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3)))
  
  
  def test_plantcv_readbayer_variablenumbergradients_bg():
      # Test with debug = None
      pcv.params.debug = None
      img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER),
                                          bayerpattern="BG", alg="variablenumbergradients")
      assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3)))
  
  
  def test_plantcv_readbayer_variablenumbergradients_gb():
      # Test with debug = None
      pcv.params.debug = None
      img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER),
                                          bayerpattern="GB", alg="variablenumbergradients")
      assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3)))
  
  
  def test_plantcv_readbayer_variablenumbergradients_rg():
      # Test with debug = None
      pcv.params.debug = None
      img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER),
                                          bayerpattern="RG", alg="variablenumbergradients")
      assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3)))
  
  
  def test_plantcv_readbayer_variablenumbergradients_gr():
      # Test with debug = None
      pcv.params.debug = None
      img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER),
                                          bayerpattern="GR", alg="variablenumbergradients")
      assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3)))
  
  
  def test_plantcv_readbayer_default_bad_input():
      # Test with debug = None
      pcv.params.debug = None
      with pytest.raises(RuntimeError):
          _, _, _ = pcv.readbayer(filename=os.path.join(TEST_DATA, "no-image.png"), bayerpattern="GR", alg="default")
  
  
  def test_plantcv_rectangle_mask():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_rectangle_mask")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      img_color = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.rectangle_mask(img=img, p1=(0, 0), p2=(2454, 2056), color="white")
      _ = pcv.rectangle_mask(img=img, p1=(0, 0), p2=(2454, 2056), color="white")
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.rectangle_mask(img=img_color, p1=(0, 0), p2=(2454, 2056), color="gray")
      # Test with debug = None
      pcv.params.debug = None
      masked, hist, contour, heir = pcv.rectangle_mask(img=img, p1=(0, 0), p2=(2454, 2056), color="black")
      maskedsum = np.sum(masked)
      imgsum = np.sum(img)
      assert maskedsum < imgsum
  
  
  def test_plantcv_rectangle_mask_bad_input():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_rectangle_mask")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      # Test with debug = None
      pcv.params.debug = None
      with pytest.raises(RuntimeError):
          _ = pcv.rectangle_mask(img=img, p1=(0, 0), p2=(2454, 2056), color="whit")
  
  
  def test_plantcv_report_size_marker_detect():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_report_size_marker_detect")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MARKER), -1)
      # ROI contour
      roi_contour = [np.array([[[3550, 850]], [[3550, 1349]], [[4049, 1349]], [[4049, 850]]], dtype=np.int32)]
      roi_hierarchy = np.array([[[-1, -1, -1, -1]]], dtype=np.int32)
  
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.report_size_marker_area(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, marker='detect',
                                      objcolor='light', thresh_channel='s', thresh=120, label="prefix")
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.report_size_marker_area(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, marker='detect',
                                      objcolor='light', thresh_channel='s', thresh=120)
      # Test with debug = None
      pcv.params.debug = None
      images = pcv.report_size_marker_area(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, marker='detect',
                                           objcolor='light', thresh_channel='s', thresh=120)
      pcv.outputs.clear()
      assert len(images) != 0
  
  
  def test_plantcv_report_size_marker_define():
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MARKER), -1)
      # ROI contour
      roi_contour = [np.array([[[3550, 850]], [[3550, 1349]], [[4049, 1349]], [[4049, 850]]], dtype=np.int32)]
      roi_hierarchy = np.array([[[-1, -1, -1, -1]]], dtype=np.int32)
  
      # Test with debug = None
      pcv.params.debug = None
      images = pcv.report_size_marker_area(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, marker='define',
                                           objcolor='light', thresh_channel='s', thresh=120)
      assert len(images) != 0
  
  
  def test_plantcv_report_size_marker_grayscale_input():
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      # ROI contour
      roi_contour = [np.array([[[0, 0]], [[0, 49]], [[49, 49]], [[49, 0]]], dtype=np.int32)]
      roi_hierarchy = np.array([[[-1, -1, -1, -1]]], dtype=np.int32)
  
      # Test with debug = None
      pcv.params.debug = None
      images = pcv.report_size_marker_area(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, marker='define',
                                           objcolor='light', thresh_channel='s', thresh=120)
      assert len(images) != 0
  
  
  def test_plantcv_report_size_marker_bad_marker_input():
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MARKER), -1)
      # ROI contour
      roi_contour = [np.array([[[3550, 850]], [[3550, 1349]], [[4049, 1349]], [[4049, 850]]], dtype=np.int32)]
      roi_hierarchy = np.array([[[-1, -1, -1, -1]]], dtype=np.int32)
      with pytest.raises(RuntimeError):
          _ = pcv.report_size_marker_area(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, marker='none',
                                          objcolor='light', thresh_channel='s', thresh=120)
  
  
  def test_plantcv_report_size_marker_bad_threshold_input():
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MARKER), -1)
      # ROI contour
      roi_contour = [np.array([[[3550, 850]], [[3550, 1349]], [[4049, 1349]], [[4049, 850]]], dtype=np.int32)]
      roi_hierarchy = np.array([[[-1, -1, -1, -1]]], dtype=np.int32)
      with pytest.raises(RuntimeError):
          _ = pcv.report_size_marker_area(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, marker='detect',
                                          objcolor='light', thresh_channel=None, thresh=120)
  
  
  def test_plantcv_rgb2gray_cmyk():
      # Test with debug = None
      pcv.params.debug = None
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      c = pcv.rgb2gray_cmyk(rgb_img=img, channel="c")
      # Assert that the output image has the dimensions of the input image but is only a single channel
      assert all([i == j] for i, j in zip(np.shape(c), TEST_GRAY_DIM))
  
  
  def test_plantcv_rgb2gray_cmyk_bad_channel():
      # Test with debug = None
      pcv.params.debug = None
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      with pytest.raises(RuntimeError):
          # Channel S is not in CMYK
          _ = pcv.rgb2gray_cmyk(rgb_img=img, channel="s")
  
  
  def test_plantcv_rgb2gray_hsv():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_rgb2gray_hsv")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.rgb2gray_hsv(rgb_img=img, channel="s")
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.rgb2gray_hsv(rgb_img=img, channel="s")
      # Test with debug = None
      pcv.params.debug = None
      s = pcv.rgb2gray_hsv(rgb_img=img, channel="s")
      # Assert that the output image has the dimensions of the input image but is only a single channel
      assert all([i == j] for i, j in zip(np.shape(s), TEST_GRAY_DIM))
  
  
  def test_plantcv_rgb2gray_hsv_bad_input():
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      pcv.params.debug = None
      with pytest.raises(RuntimeError):
          _ = pcv.rgb2gray_hsv(rgb_img=img, channel="l")
  
  
  def test_plantcv_rgb2gray_lab():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_rgb2gray_lab")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.rgb2gray_lab(rgb_img=img, channel='b')
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.rgb2gray_lab(rgb_img=img, channel='b')
      # Test with debug = None
      pcv.params.debug = None
      b = pcv.rgb2gray_lab(rgb_img=img, channel='b')
      # Assert that the output image has the dimensions of the input image but is only a single channel
      assert all([i == j] for i, j in zip(np.shape(b), TEST_GRAY_DIM))
  
  
  def test_plantcv_rgb2gray_lab_bad_input():
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      pcv.params.debug = None
      with pytest.raises(RuntimeError):
          _ = pcv.rgb2gray_lab(rgb_img=img, channel="v")
  
  
  def test_plantcv_rgb2gray():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_rgb2gray")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # Test with debug = None
      pcv.params.debug = None
      gray = pcv.rgb2gray(rgb_img=img)
      # Assert that the output image has the dimensions of the input image but is only a single channel
      assert all([i == j] for i, j in zip(np.shape(gray), TEST_GRAY_DIM))
  
  
  def test_plantcv_roi2mask():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_acute_vertex")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_VIS_SMALL))
      contours_npz = np.load(os.path.join(TEST_DATA, TEST_VIS_COMP_CONTOUR), encoding="latin1")
      obj_contour = contours_npz['arr_0']
      pcv.params.debug = "plot"
      _ = pcv.roi.roi2mask(img=img, contour=obj_contour)
      pcv.params.debug = "print"
      mask = pcv.roi.roi2mask(img=img, contour=obj_contour)
      assert np.shape(mask)[0:2] == np.shape(img)[0:2] and np.sum(mask) == 255
  
  
  def test_plantcv_roi_objects():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_roi_objects")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      roi_contour_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_ROI_CONTOUR), encoding="latin1")
      roi_contour = [roi_contour_npz[arr_n] for arr_n in roi_contour_npz]
      roi_hierarchy_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_ROI_HIERARCHY), encoding="latin1")
      roi_hierarchy = roi_hierarchy_npz['arr_0']
      object_contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_OBJECT_CONTOURS), encoding="latin1")
      object_contours = [object_contours_npz[arr_n] for arr_n in object_contours_npz]
      object_hierarchy_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_OBJECT_HIERARCHY), encoding="latin1")
      object_hierarchy = object_hierarchy_npz['arr_0']
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.roi_objects(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy,
                          object_contour=object_contours, obj_hierarchy=object_hierarchy, roi_type="largest")
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.roi_objects(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy,
                          object_contour=object_contours, obj_hierarchy=object_hierarchy, roi_type="partial")
      # Test with debug = None and roi_type = cutto
      pcv.params.debug = None
      _ = pcv.roi_objects(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy,
                          object_contour=object_contours, obj_hierarchy=object_hierarchy, roi_type="cutto")
      # Test with debug = None
      kept_contours, kept_hierarchy, mask, area = pcv.roi_objects(img=img, roi_contour=roi_contour,
                                                                  roi_hierarchy=roi_hierarchy,
                                                                  object_contour=object_contours,
                                                                  obj_hierarchy=object_hierarchy, roi_type="partial")
      # Assert that the contours were filtered as expected
      assert len(kept_contours) == 1891
  
  
  def test_plantcv_roi_objects_bad_input():
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      roi_contour_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_ROI_CONTOUR), encoding="latin1")
      roi_contour = [roi_contour_npz[arr_n] for arr_n in roi_contour_npz]
      roi_hierarchy_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_ROI_HIERARCHY), encoding="latin1")
      roi_hierarchy = roi_hierarchy_npz['arr_0']
      object_contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_OBJECT_CONTOURS), encoding="latin1")
      object_contours = [object_contours_npz[arr_n] for arr_n in object_contours_npz]
      object_hierarchy_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_OBJECT_HIERARCHY), encoding="latin1")
      object_hierarchy = object_hierarchy_npz['arr_0']
      pcv.params.debug = None
      with pytest.raises(RuntimeError):
          _ = pcv.roi_objects(img=img, roi_type="cut", roi_contour=roi_contour, roi_hierarchy=roi_hierarchy,
                              object_contour=object_contours, obj_hierarchy=object_hierarchy)
  
  
  def test_plantcv_roi_objects_grayscale_input():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_roi_objects_grayscale_input")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), 0)
      roi_contour_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_ROI_CONTOUR), encoding="latin1")
      roi_contour = [roi_contour_npz[arr_n] for arr_n in roi_contour_npz]
      roi_hierarchy_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_ROI_HIERARCHY), encoding="latin1")
      roi_hierarchy = roi_hierarchy_npz['arr_0']
      object_contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_OBJECT_CONTOURS), encoding="latin1")
      object_contours = [object_contours_npz[arr_n] for arr_n in object_contours_npz]
      object_hierarchy_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_OBJECT_HIERARCHY), encoding="latin1")
      object_hierarchy = object_hierarchy_npz['arr_0']
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      kept_contours, kept_hierarchy, mask, area = pcv.roi_objects(img=img, roi_type="partial", roi_contour=roi_contour,
                                                                  roi_hierarchy=roi_hierarchy,
                                                                  object_contour=object_contours,
                                                                  obj_hierarchy=object_hierarchy)
      # Assert that the contours were filtered as expected
      assert len(kept_contours) == 1891
  
  def test_plantcv_rotate():
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      rotated = pcv.rotate(img=img, rotation_deg=45, crop=True)
      imgavg = np.average(img)
      rotateavg = np.average(rotated)
      assert rotateavg != imgavg
  
  def test_plantcv_transform_rotate():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_rotate_img")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.transform.rotate(img=img, rotation_deg=45, crop=True)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.transform.rotate(img=img, rotation_deg=45, crop=True)
      # Test with debug = None
      pcv.params.debug = None
      rotated = pcv.transform.rotate(img=img, rotation_deg=45, crop=True)
      imgavg = np.average(img)
      rotateavg = np.average(rotated)
      assert rotateavg != imgavg
  
  
  def test_plantcv_transform_rotate_gray():
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.transform.rotate(img=img, rotation_deg=45, crop=False)
      # Test with debug = None
      pcv.params.debug = None
      rotated = pcv.transform.rotate(img=img, rotation_deg=45, crop=False)
      imgavg = np.average(img)
      rotateavg = np.average(rotated)
      assert rotateavg != imgavg
  
  
  def test_plantcv_scale_features():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_scale_features")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_MASK_SMALL), -1)
      contours_npz = np.load(os.path.join(TEST_DATA, TEST_VIS_COMP_CONTOUR), encoding="latin1")
      obj_contour = contours_npz['arr_0']
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.scale_features(obj=obj_contour, mask=mask, points=TEST_ACUTE_RESULT, line_position=50)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.scale_features(obj=obj_contour, mask=mask, points=TEST_ACUTE_RESULT, line_position='NA')
      # Test with debug = None
      pcv.params.debug = None
      points_rescaled, centroid_rescaled, bottomline_rescaled = pcv.scale_features(obj=obj_contour, mask=mask,
                                                                                   points=TEST_ACUTE_RESULT,
                                                                                   line_position=50)
      assert len(points_rescaled) == 23
  
  
  def test_plantcv_scale_features_bad_input():
      mask = np.array([])
      obj_contour = np.array([])
      pcv.params.debug = None
      result = pcv.scale_features(obj=obj_contour, mask=mask, points=TEST_ACUTE_RESULT, line_position=50)
      assert all([i == j] for i, j in zip(result, [("NA", "NA"), ("NA", "NA"), ("NA", "NA")]))
  
  
  def test_plantcv_scharr_filter():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_scharr_filter")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      pcv.params.debug = "print"
      # Test with debug = "print"
      _ = pcv.scharr_filter(img=img, dx=1, dy=0, scale=1)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.scharr_filter(img=img, dx=1, dy=0, scale=1)
      # Test with debug = None
      pcv.params.debug = None
      scharr_img = pcv.scharr_filter(img=img, dx=1, dy=0, scale=1)
      # Assert that the output image has the dimensions of the input image
      assert all([i == j] for i, j in zip(np.shape(scharr_img), TEST_GRAY_DIM))
  
  
  def test_plantcv_shift_img():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_shift_img")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.shift_img(img=img, number=300, side="top")
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.shift_img(img=img, number=300, side="top")
      # Test with debug = "plot"
      _ = pcv.shift_img(img=img, number=300, side="bottom")
      # Test with debug = "plot"
      _ = pcv.shift_img(img=img, number=300, side="right")
      # Test with debug = "plot"
      _ = pcv.shift_img(img=mask, number=300, side="left")
      # Test with debug = None
      pcv.params.debug = None
      rotated = pcv.shift_img(img=img, number=300, side="top")
      imgavg = np.average(img)
      shiftavg = np.average(rotated)
      assert shiftavg != imgavg
  
  
  def test_plantcv_shift_img_bad_input():
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      with pytest.raises(RuntimeError):
          pcv.params.debug = None
          _ = pcv.shift_img(img=img, number=-300, side="top")
  
  
  def test_plantcv_shift_img_bad_side_input():
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      with pytest.raises(RuntimeError):
          pcv.params.debug = None
          _ = pcv.shift_img(img=img, number=300, side="starboard")
  
  
  def test_plantcv_sobel_filter():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_sobel_filter")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.sobel_filter(gray_img=img, dx=1, dy=0, ksize=1)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.sobel_filter(gray_img=img, dx=1, dy=0, ksize=1)
      # Test with debug = None
      pcv.params.debug = None
      sobel_img = pcv.sobel_filter(gray_img=img, dx=1, dy=0, ksize=1)
      # Assert that the output image has the dimensions of the input image
      assert all([i == j] for i, j in zip(np.shape(sobel_img), TEST_GRAY_DIM))
  
  
  def test_plantcv_stdev_filter():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_sobel_filter")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY_SMALL), -1)
      pcv.params.debug = "plot"
      _ = pcv.stdev_filter(img=img, ksize=11)
      pcv.params.debug = "print"
      filter_img = pcv.stdev_filter(img=img, ksize=11)
      assert (np.shape(filter_img) == np.shape(img))
  
  
  def test_plantcv_watershed_segmentation():
      # Clear previous outputs
      pcv.outputs.clear()
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_watershed_segmentation")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_CROPPED))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_CROPPED_MASK), -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.watershed_segmentation(rgb_img=img, mask=mask, distance=10, label="prefix")
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.watershed_segmentation(rgb_img=img, mask=mask, distance=10)
      # Test with debug = None
      pcv.params.debug = None
      _ = pcv.watershed_segmentation(rgb_img=img, mask=mask, distance=10)
      assert pcv.outputs.observations['default']['estimated_object_count']['value'] > 9
  
  
  def test_plantcv_white_balance_gray_16bit():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_white_balance_gray_16bit")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_NIR_MASK), -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.white_balance(img=img, mode='hist', roi=(5, 5, 80, 80))
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.white_balance(img=img, mode='max', roi=(5, 5, 80, 80))
      # Test without an ROI
      pcv.params.debug = None
      _ = pcv.white_balance(img=img, mode='hist', roi=None)
      # Test with debug = None
      white_balanced = pcv.white_balance(img=img, roi=(5, 5, 80, 80))
      imgavg = np.average(img)
      balancedavg = np.average(white_balanced)
      assert balancedavg != imgavg
  
  
  def test_plantcv_white_balance_gray_8bit():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_white_balance_gray_8bit")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_NIR_MASK))
      img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.white_balance(img=img, mode='hist', roi=(5, 5, 80, 80))
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.white_balance(img=img, mode='max', roi=(5, 5, 80, 80))
      # Test without an ROI
      pcv.params.debug = None
      _ = pcv.white_balance(img=img, mode='hist', roi=None)
      # Test with debug = None
      white_balanced = pcv.white_balance(img=img, roi=(5, 5, 80, 80))
      imgavg = np.average(img)
      balancedavg = np.average(white_balanced)
      assert balancedavg != imgavg
  
  
  def test_plantcv_white_balance_rgb():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_white_balance_rgb")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MARKER))
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.white_balance(img=img, mode='hist', roi=(5, 5, 80, 80))
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.white_balance(img=img, mode='max', roi=(5, 5, 80, 80))
      # Test without an ROI
      pcv.params.debug = None
      _ = pcv.white_balance(img=img, mode='hist', roi=None)
      # Test with debug = None
      white_balanced = pcv.white_balance(img=img, roi=(5, 5, 80, 80))
      imgavg = np.average(img)
      balancedavg = np.average(white_balanced)
      assert balancedavg != imgavg
  
  
  def test_plantcv_white_balance_bad_input():
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_NIR_MASK), -1)
      # Test with debug = None
      with pytest.raises(RuntimeError):
          pcv.params.debug = "plot"
          _ = pcv.white_balance(img=img, mode='hist', roi=(5, 5, 5, 5, 5))
  
  
  def test_plantcv_white_balance_bad_mode_input():
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MARKER))
      # Test with debug = None
      with pytest.raises(RuntimeError):
          pcv.params.debug = "plot"
          _ = pcv.white_balance(img=img, mode='histogram', roi=(5, 5, 80, 80))
  
  
  def test_plantcv_white_balance_bad_input_int():
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_NIR_MASK), -1)
      # Test with debug = None
      with pytest.raises(RuntimeError):
          pcv.params.debug = "plot"
          _ = pcv.white_balance(img=img, mode='hist', roi=(5., 5, 5, 5))
  
  
  def test_plantcv_x_axis_pseudolandmarks():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_x_axis_pseudolandmarks_debug")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      img = cv2.imread(os.path.join(TEST_DATA, TEST_VIS_SMALL))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_MASK_SMALL), -1)
      contours_npz = np.load(os.path.join(TEST_DATA, TEST_VIS_COMP_CONTOUR), encoding="latin1")
      obj_contour = contours_npz['arr_0']
      pcv.params.debug = "print"
      _ = pcv.x_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.x_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img, label="prefix")
      _ = pcv.x_axis_pseudolandmarks(obj=np.array([[0, 0], [0, 0]]), mask=np.array([[0, 0], [0, 0]]), img=img)
      _ = pcv.x_axis_pseudolandmarks(obj=np.array(([[89, 222]], [[252, 39]], [[89, 207]])),
                                     mask=np.array(([[42, 161]], [[2, 47]], [[211, 222]])), img=img)
  
      _ = pcv.x_axis_pseudolandmarks(obj=(), mask=mask, img=img)
      # Test with debug = None
      pcv.params.debug = None
      top, bottom, center_v = pcv.x_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img)
      pcv.outputs.clear()
      assert all([all([i == j] for i, j in zip(np.shape(top), (20, 1, 2))),
                  all([i == j] for i, j in zip(np.shape(bottom), (20, 1, 2))),
                  all([i == j] for i, j in zip(np.shape(center_v), (20, 1, 2)))])
  
  
  def test_plantcv_x_axis_pseudolandmarks_small_obj():
      img = cv2.imread(os.path.join(TEST_DATA, TEST_VIS_SMALL_PLANT))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_MASK_SMALL_PLANT), -1)
      contours_npz = np.load(os.path.join(TEST_DATA, TEST_VIS_COMP_CONTOUR_SMALL_PLANT), encoding="latin1")
      obj_contour = contours_npz['arr_0']
      # Test with debug = "print"
      pcv.params.debug = "print"
      _, _, _ = pcv.x_axis_pseudolandmarks(obj=[], mask=mask, img=img)
      _, _, _ = pcv.x_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _, _, _ = pcv.x_axis_pseudolandmarks(obj=[], mask=mask, img=img)
      top, bottom, center_v = pcv.x_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img)
      assert all([all([i == j] for i, j in zip(np.shape(top), (20, 1, 2))),
                  all([i == j] for i, j in zip(np.shape(bottom), (20, 1, 2))),
                  all([i == j] for i, j in zip(np.shape(center_v), (20, 1, 2)))])
  
  
  def test_plantcv_x_axis_pseudolandmarks_bad_input():
      img = np.array([])
      mask = np.array([])
      obj_contour = np.array([])
      pcv.params.debug = None
      result = pcv.x_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img)
      assert all([i == j] for i, j in zip(result, [("NA", "NA"), ("NA", "NA"), ("NA", "NA")]))
  
  
  def test_plantcv_x_axis_pseudolandmarks_bad_obj_input():
      img = cv2.imread(os.path.join(TEST_DATA, TEST_VIS_SMALL_PLANT))
      with pytest.raises(RuntimeError):
          _ = pcv.x_axis_pseudolandmarks(obj=np.array([[-2, -2], [-2, -2]]), mask=np.array([[-2, -2], [-2, -2]]), img=img)
  
  
  def test_plantcv_y_axis_pseudolandmarks():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_y_axis_pseudolandmarks_debug")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      img = cv2.imread(os.path.join(TEST_DATA, TEST_VIS_SMALL))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_MASK_SMALL), -1)
      contours_npz = np.load(os.path.join(TEST_DATA, TEST_VIS_COMP_CONTOUR), encoding="latin1")
      obj_contour = contours_npz['arr_0']
      pcv.params.debug = "print"
      _ = pcv.y_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img, label="prefix")
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.y_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img)
      pcv.outputs.clear()
      _ = pcv.y_axis_pseudolandmarks(obj=[], mask=mask, img=img)
      _ = pcv.y_axis_pseudolandmarks(obj=(), mask=mask, img=img)
      _ = pcv.y_axis_pseudolandmarks(obj=np.array(([[89, 222]], [[252, 39]], [[89, 207]])),
                                     mask=np.array(([[42, 161]], [[2, 47]], [[211, 222]])), img=img)
      _ = pcv.y_axis_pseudolandmarks(obj=np.array(([[21, 11]], [[159, 155]], [[237, 11]])),
                                     mask=np.array(([[38, 54]], [[144, 169]], [[81, 137]])), img=img)
      # Test with debug = None
      pcv.params.debug = None
      left, right, center_h = pcv.y_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img)
      pcv.outputs.clear()
      assert all([all([i == j] for i, j in zip(np.shape(left), (20, 1, 2))),
                  all([i == j] for i, j in zip(np.shape(right), (20, 1, 2))),
                  all([i == j] for i, j in zip(np.shape(center_h), (20, 1, 2)))])
  
  
  def test_plantcv_y_axis_pseudolandmarks_small_obj():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_y_axis_pseudolandmarks_debug")
      os.mkdir(cache_dir)
      img = cv2.imread(os.path.join(TEST_DATA, TEST_VIS_SMALL_PLANT))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_MASK_SMALL_PLANT), -1)
      contours_npz = np.load(os.path.join(TEST_DATA, TEST_VIS_COMP_CONTOUR_SMALL_PLANT), encoding="latin1")
      obj_contour = contours_npz['arr_0']
      # Test with debug = "print"
      pcv.params.debug = "print"
      _, _, _ = pcv.y_axis_pseudolandmarks(obj=[], mask=mask, img=img)
      _, _, _ = pcv.y_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      pcv.outputs.clear()
      left, right, center_h = pcv.y_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img)
      pcv.outputs.clear()
      assert all([all([i == j] for i, j in zip(np.shape(left), (20, 1, 2))),
                  all([i == j] for i, j in zip(np.shape(right), (20, 1, 2))),
                  all([i == j] for i, j in zip(np.shape(center_h), (20, 1, 2)))])
  
  
  def test_plantcv_y_axis_pseudolandmarks_bad_input():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_y_axis_pseudolandmarks_debug")
      os.mkdir(cache_dir)
      img = np.array([])
      mask = np.array([])
      obj_contour = np.array([])
      pcv.params.debug = None
      result = pcv.y_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img)
      pcv.outputs.clear()
      assert all([i == j] for i, j in zip(result, [("NA", "NA"), ("NA", "NA"), ("NA", "NA")]))
  
  
  def test_plantcv_y_axis_pseudolandmarks_bad_obj_input():
      img = cv2.imread(os.path.join(TEST_DATA, TEST_VIS_SMALL_PLANT))
      with pytest.raises(RuntimeError):
          _ = pcv.y_axis_pseudolandmarks(obj=np.array([[-2, -2], [-2, -2]]), mask=np.array([[-2, -2], [-2, -2]]), img=img)
  
  
  def test_plantcv_background_subtraction():
      # List to hold result of all tests.
      truths = []
      fg_img = cv2.imread(os.path.join(TEST_DATA, TEST_FOREGROUND))
      bg_img = cv2.imread(os.path.join(TEST_DATA, TEST_BACKGROUND))
      big_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # Testing if background subtraction is actually still working.
      # This should return an array whose sum is greater than one
      pcv.params.debug = None
      fgmask = pcv.background_subtraction(background_image=bg_img, foreground_image=fg_img)
      truths.append(np.sum(fgmask) > 0)
      fgmask = pcv.background_subtraction(background_image=big_img, foreground_image=bg_img)
      truths.append(np.sum(fgmask) > 0)
      # The same foreground subtracted from itself should be 0
      fgmask = pcv.background_subtraction(background_image=fg_img, foreground_image=fg_img)
      truths.append(np.sum(fgmask) == 0)
      # The same background subtracted from itself should be 0
      fgmask = pcv.background_subtraction(background_image=bg_img, foreground_image=bg_img)
      truths.append(np.sum(fgmask) == 0)
      # All of these should be true for the function to pass testing.
      assert (all(truths))
  
  
  def test_plantcv_background_subtraction_debug():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_background_subtraction_debug")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # List to hold result of all tests.
      truths = []
      fg_img = cv2.imread(os.path.join(TEST_DATA, TEST_FOREGROUND))
      bg_img = cv2.imread(os.path.join(TEST_DATA, TEST_BACKGROUND))
      # Test with debug = "print"
      pcv.params.debug = "print"
      fgmask = pcv.background_subtraction(background_image=bg_img, foreground_image=fg_img)
      truths.append(np.sum(fgmask) > 0)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      fgmask = pcv.background_subtraction(background_image=bg_img, foreground_image=fg_img)
      truths.append(np.sum(fgmask) > 0)
      # All of these should be true for the function to pass testing.
      assert (all(truths))
  
  
  def test_plantcv_background_subtraction_bad_img_type():
      fg_color = cv2.imread(os.path.join(TEST_DATA, TEST_FOREGROUND))
      bg_gray = cv2.imread(os.path.join(TEST_DATA, TEST_BACKGROUND), 0)
      pcv.params.debug = None
      with pytest.raises(RuntimeError):
          _ = pcv.background_subtraction(background_image=bg_gray, foreground_image=fg_color)
  
  
  def test_plantcv_background_subtraction_different_sizes():
      fg_img = cv2.imread(os.path.join(TEST_DATA, TEST_FOREGROUND))
      bg_img = cv2.imread(os.path.join(TEST_DATA, TEST_BACKGROUND))
      bg_shp = np.shape(bg_img)  # type: tuple
      bg_img_resized = cv2.resize(bg_img, (int(bg_shp[0] / 2), int(bg_shp[1] / 2)), interpolation=cv2.INTER_AREA)
      pcv.params.debug = None
      fgmask = pcv.background_subtraction(background_image=bg_img_resized, foreground_image=fg_img)
      assert np.sum(fgmask) > 0
  
  
  def test_plantcv_spatial_clustering_dbscan():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_spatial_clustering_dbscan")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI_MASK), -1)
      pcv.params.debug = "print"
      _ = pcv.spatial_clustering(img, algorithm="DBSCAN", min_cluster_size=10, max_distance=None)
      pcv.params.debug = "plot"
      spmask = pcv.spatial_clustering(img, algorithm="DBSCAN", min_cluster_size=10, max_distance=None)
      assert len(spmask[1]) == 2
  
  
  def test_plantcv_spatial_clustering_optics():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_spatial_clustering_optics")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI_MASK), -1)
      pcv.params.debug = None
      spmask = pcv.spatial_clustering(img, algorithm="OPTICS", min_cluster_size=100, max_distance=5000)
      assert len(spmask[1]) == 2
  
  
  def test_plantcv_spatial_clustering_badinput():
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI_MASK), -1)
      pcv.params.debug = None
      with pytest.raises(NameError):
          _ = pcv.spatial_clustering(img, algorithm="Hydra", min_cluster_size=5, max_distance=100)
  
  
  # ##############################
  # Tests for the learn subpackage
  # ##############################
  def test_plantcv_learn_naive_bayes():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_learn_naive_bayes")
      os.mkdir(cache_dir)
      # Make image and mask directories in the cache directory
      imgdir = os.path.join(cache_dir, "images")
      maskdir = os.path.join(cache_dir, "masks")
      if not os.path.exists(imgdir):
          os.mkdir(imgdir)
      if not os.path.exists(maskdir):
          os.mkdir(maskdir)
      # Copy and image and mask to the image/mask directories
      shutil.copyfile(os.path.join(TEST_DATA, TEST_VIS_SMALL), os.path.join(imgdir, "image.png"))
      shutil.copyfile(os.path.join(TEST_DATA, TEST_MASK_SMALL), os.path.join(maskdir, "image.png"))
      # Run the naive Bayes training module
      outfile = os.path.join(cache_dir, "naive_bayes_pdfs.txt")
      plantcv.learn.naive_bayes(imgdir=imgdir, maskdir=maskdir, outfile=outfile, mkplots=True)
      assert os.path.exists(outfile)
  
  
  def test_plantcv_learn_naive_bayes_multiclass():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_learn_naive_bayes_multiclass")
      os.mkdir(cache_dir)
      # Run the naive Bayes multiclass training module
      outfile = os.path.join(cache_dir, "naive_bayes_multiclass_pdfs.txt")
      plantcv.learn.naive_bayes_multiclass(samples_file=os.path.join(TEST_DATA, TEST_SAMPLED_RGB_POINTS), outfile=outfile,
                                           mkplots=True)
      assert os.path.exists(outfile)
  
  
  # ####################################
  # Tests for the morphology subpackage
  # ####################################
  def test_plantcv_morphology_segment_curvature():
      # Clear previous outputs
      pcv.outputs.clear()
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_curvature")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON_PRUNED), -1)
      pcv.params.debug = "print"
      segmented_img, seg_objects = pcv.morphology.segment_skeleton(skel_img=skeleton)
      pcv.outputs.clear()
      _ = pcv.morphology.segment_curvature(segmented_img, seg_objects, label="prefix")
      pcv.params.debug = "plot"
      pcv.outputs.clear()
      _ = pcv.morphology.segment_curvature(segmented_img, seg_objects)
      assert len(pcv.outputs.observations['default']['segment_curvature']['value']) == 22
  
  
  def test_plantcv_morphology_check_cycles():
      # Clear previous outputs
      pcv.outputs.clear()
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_branches")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      pcv.params.debug = "print"
      _ = pcv.morphology.check_cycles(mask, label="prefix")
      pcv.params.debug = "plot"
      _ = pcv.morphology.check_cycles(mask)
      pcv.params.debug = None
      _ = pcv.morphology.check_cycles(mask)
      assert pcv.outputs.observations['default']['num_cycles']['value'] == 1
  
  
  def test_plantcv_morphology_find_branch_pts():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_branches")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1)
      pcv.params.debug = "print"
      _ = pcv.morphology.find_branch_pts(skel_img=skeleton, mask=mask, label="prefix")
      pcv.params.debug = "plot"
      _ = pcv.morphology.find_branch_pts(skel_img=skeleton)
      pcv.params.debug = None
      branches = pcv.morphology.find_branch_pts(skel_img=skeleton)
      assert np.sum(branches) == 9435
  
  
  def test_plantcv_morphology_find_tips():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_tips")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1)
      pcv.params.debug = "print"
      _ = pcv.morphology.find_tips(skel_img=skeleton, mask=mask, label="prefix")
      pcv.params.debug = "plot"
      _ = pcv.morphology.find_tips(skel_img=skeleton)
      pcv.params.debug = None
      tips = pcv.morphology.find_tips(skel_img=skeleton)
      assert np.sum(tips) == 9435
  
  
  def test_plantcv_morphology_prune():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_pruned")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1)
      pcv.params.debug = "print"
      _ = pcv.morphology.prune(skel_img=skeleton, size=1)
      pcv.params.debug = "plot"
      _ = pcv.morphology.prune(skel_img=skeleton, size=1, mask=skeleton)
      pcv.params.debug = None
      pruned_img, _, _ = pcv.morphology.prune(skel_img=skeleton, size=3)
      assert np.sum(pruned_img) < np.sum(skeleton)
  
  
  def test_plantcv_morphology_prune_size0():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_pruned")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1)
      pruned_img, _, _ = pcv.morphology.prune(skel_img=skeleton, size=0)
      assert np.sum(pruned_img) == np.sum(skeleton)
  
  
  def test_plantcv_morphology_iterative_prune():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_pruned")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1)
      pruned_img = pcv.morphology._iterative_prune(skel_img=skeleton, size=3)
      assert np.sum(pruned_img) < np.sum(skeleton)
  
  
  def test_plantcv_morphology_segment_skeleton():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_skeleton")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1)
      pcv.params.debug = "print"
      _ = pcv.morphology.segment_skeleton(skel_img=skeleton, mask=mask)
      pcv.params.debug = "plot"
      segmented_img, segment_objects = pcv.morphology.segment_skeleton(skel_img=skeleton)
      assert len(segment_objects) == 73
  
  
  def test_plantcv_morphology_fill_segments():
      # Clear previous outputs
      pcv.outputs.clear()
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      obj_dic = np.load(os.path.join(TEST_DATA, TEST_SKELETON_OBJECTS))
      obj = []
      for key, val in obj_dic.items():
          obj.append(val)
      pcv.params.debug = None
      _ = pcv.morphology.fill_segments(mask, obj)
      tests = [pcv.outputs.observations['default']['segment_area']['value'][42] == 5529,
               pcv.outputs.observations['default']['segment_area']['value'][20] == 5057,
               pcv.outputs.observations['default']['segment_area']['value'][49] == 3323]
      assert all(tests)
  
  
  def test_plantcv_morphology_fill_segments_with_stem():
      # Clear previous outputs
      pcv.outputs.clear()
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      obj_dic = np.load(os.path.join(TEST_DATA, TEST_SKELETON_OBJECTS))
      obj = []
      for key, val in obj_dic.items():
          obj.append(val)
  
      stem_obj = obj[0:4]
      pcv.params.debug = None
      _ = pcv.morphology.fill_segments(mask, obj, stem_obj)
      num_objects = len(pcv.outputs.observations['default']['leaf_area']['value'])
      assert num_objects == 69
  
  
  def test_plantcv_morphology_segment_angle():
      # Clear previous outputs
      pcv.outputs.clear()
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_angles")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON_PRUNED), -1)
      pcv.params.debug = "print"
      segmented_img, segment_objects = pcv.morphology.segment_skeleton(skel_img=skeleton)
      _ = pcv.morphology.segment_angle(segmented_img=segmented_img, objects=segment_objects, label="prefix")
      pcv.params.debug = "plot"
      _ = pcv.morphology.segment_angle(segmented_img, segment_objects)
      assert len(pcv.outputs.observations['default']['segment_angle']['value']) == 22
  
  
  def test_plantcv_morphology_segment_angle_overflow():
      # Clear previous outputs
      pcv.outputs.clear()
      # Don't prune, would usually give overflow error without extra if statement in segment_angle
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_angles")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1)
      segmented_img, segment_objects = pcv.morphology.segment_skeleton(skel_img=skeleton)
      _ = pcv.morphology.segment_angle(segmented_img, segment_objects)
      assert len(pcv.outputs.observations['default']['segment_angle']['value']) == 73
  
  
  def test_plantcv_morphology_segment_euclidean_length():
      # Clear previous outputs
      pcv.outputs.clear()
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_eu_length")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON_PRUNED), -1)
      pcv.params.debug = "print"
      segmented_img, segment_objects = pcv.morphology.segment_skeleton(skel_img=skeleton)
      _ = pcv.morphology.segment_euclidean_length(segmented_img, segment_objects, label="prefix")
      pcv.params.debug = "plot"
      _ = pcv.morphology.segment_euclidean_length(segmented_img, segment_objects)
      assert len(pcv.outputs.observations['default']['segment_eu_length']['value']) == 22
  
  
  def test_plantcv_morphology_segment_euclidean_length_bad_input():
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      skel = pcv.morphology.skeletonize(mask=mask)
      pcv.params.debug = None
      segmented_img, segment_objects = pcv.morphology.segment_skeleton(skel_img=skel)
      with pytest.raises(RuntimeError):
          _ = pcv.morphology.segment_euclidean_length(segmented_img, segment_objects)
  
  
  def test_plantcv_morphology_segment_path_length():
      # Clear previous outputs
      pcv.outputs.clear()
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_path_length")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON_PRUNED), -1)
      pcv.params.debug = "print"
      segmented_img, segment_objects = pcv.morphology.segment_skeleton(skel_img=skeleton)
      _ = pcv.morphology.segment_path_length(segmented_img, segment_objects, label="prefix")
      pcv.params.debug = "plot"
      _ = pcv.morphology.segment_path_length(segmented_img, segment_objects)
      assert len(pcv.outputs.observations['default']['segment_path_length']['value']) == 22
  
  
  def test_plantcv_morphology_skeletonize():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_skeletonize")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      input_skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1)
      pcv.params.debug = "print"
      _ = pcv.morphology.skeletonize(mask=mask)
      pcv.params.debug = "plot"
      _ = pcv.morphology.skeletonize(mask=mask)
      pcv.params.debug = None
      skeleton = pcv.morphology.skeletonize(mask=mask)
      arr = np.array(skeleton == input_skeleton)
      assert arr.all()
  
  
  def test_plantcv_morphology_segment_sort():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_sort")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1)
      segmented_img, seg_objects = pcv.morphology.segment_skeleton(skel_img=skeleton)
      pcv.params.debug = "print"
      _ = pcv.morphology.segment_sort(skeleton, seg_objects, mask=skeleton)
      pcv.params.debug = "plot"
      leaf_obj, stem_obj = pcv.morphology.segment_sort(skeleton, seg_objects)
      assert len(leaf_obj) == 36
  
  
  def test_plantcv_morphology_segment_tangent_angle():
      # Clear previous outputs
      pcv.outputs.clear()
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_tangent_angle")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      skel = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON_PRUNED), -1)
      objects = np.load(os.path.join(TEST_DATA, TEST_SKELETON_OBJECTS), encoding="latin1")
      objs = [objects[arr_n] for arr_n in objects]
      pcv.params.debug = "print"
      _ = pcv.morphology.segment_tangent_angle(skel, objs, 2, label="prefix")
      pcv.params.debug = "plot"
      _ = pcv.morphology.segment_tangent_angle(skel, objs, 2)
      assert len(pcv.outputs.observations['default']['segment_tangent_angle']['value']) == 73
  
  
  def test_plantcv_morphology_segment_id():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_tangent_angle")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      skel = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON_PRUNED), -1)
      objects = np.load(os.path.join(TEST_DATA, TEST_SKELETON_OBJECTS), encoding="latin1")
      objs = [objects[arr_n] for arr_n in objects]
      pcv.params.debug = "print"
      _ = pcv.morphology.segment_id(skel, objs)
      pcv.params.debug = "plot"
      _, labeled_img = pcv.morphology.segment_id(skel, objs, mask=skel)
      assert np.sum(labeled_img) > np.sum(skel)
  
  
  def test_plantcv_morphology_segment_insertion_angle():
      # Clear previous outputs
      pcv.outputs.clear()
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_insertion_angle")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1)
      pruned, _, _ = pcv.morphology.prune(skel_img=skeleton, size=6)
      segmented_img, seg_objects = pcv.morphology.segment_skeleton(skel_img=pruned)
      leaf_obj, stem_obj = pcv.morphology.segment_sort(pruned, seg_objects)
      pcv.params.debug = "plot"
      _ = pcv.morphology.segment_insertion_angle(pruned, segmented_img, leaf_obj, stem_obj, 3, label="prefix")
      pcv.params.debug = "print"
      _ = pcv.morphology.segment_insertion_angle(pruned, segmented_img, leaf_obj, stem_obj, 10)
      assert pcv.outputs.observations['default']['segment_insertion_angle']['value'][:6] == ['NA', 'NA', 'NA',
                                                                                          24.956918822001636,
                                                                                          50.7313343343401,
                                                                                          56.427712102130734]
  
  
  def test_plantcv_morphology_segment_insertion_angle_bad_stem():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_insertion_angle")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1)
      pruned, _, _ = pcv.morphology.prune(skel_img=skeleton, size=5)
      segmented_img, seg_objects = pcv.morphology.segment_skeleton(skel_img=pruned)
      leaf_obj, stem_obj = pcv.morphology.segment_sort(pruned, seg_objects)
      stem_obj = [leaf_obj[0], leaf_obj[10]]
      with pytest.raises(RuntimeError):
          _ = pcv.morphology.segment_insertion_angle(pruned, segmented_img, leaf_obj, stem_obj, 10)
  
  
  def test_plantcv_morphology_segment_combine():
      skel = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON_PRUNED), -1)
      segmented_img, seg_objects = pcv.morphology.segment_skeleton(skel_img=skel)
      pcv.params.debug = "plot"
      # Test with list of IDs input
      _, new_objects = pcv.morphology.segment_combine([0, 1], seg_objects, skel)
      assert len(new_objects) + 1 == len(seg_objects)
  
  
  def test_plantcv_morphology_segment_combine_lists():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_insertion_angle")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      skel = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON_PRUNED), -1)
      segmented_img, seg_objects = pcv.morphology.segment_skeleton(skel_img=skel)
      pcv.params.debug = "print"
      # Test with list of lists input
      _, new_objects = pcv.morphology.segment_combine([[0, 1, 2], [3, 4]], seg_objects, skel)
      assert len(new_objects) + 3 == len(seg_objects)
  
  
  def test_plantcv_morphology_segment_combine_bad_input():
      skel = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON_PRUNED), -1)
      segmented_img, seg_objects = pcv.morphology.segment_skeleton(skel_img=skel)
      pcv.params.debug = "plot"
      with pytest.raises(RuntimeError):
          _, new_objects = pcv.morphology.segment_combine([0.5, 1.5], seg_objects, skel)
  
  
  def test_plantcv_morphology_analyze_stem():
      # Clear previous outputs
      pcv.outputs.clear()
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_analyze_stem")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1)
      pruned, segmented_img, _ = pcv.morphology.prune(skel_img=skeleton, size=6)
      segmented_img, seg_objects = pcv.morphology.segment_skeleton(skel_img=pruned)
      leaf_obj, stem_obj = pcv.morphology.segment_sort(pruned, seg_objects)
      pcv.params.debug = "plot"
      _ = pcv.morphology.analyze_stem(rgb_img=segmented_img, stem_objects=stem_obj, label="prefix")
      pcv.params.debug = "print"
      _ = pcv.morphology.analyze_stem(rgb_img=segmented_img, stem_objects=stem_obj)
      assert pcv.outputs.observations['default']['stem_angle']['value'] == -12.531776428222656
  
  
  def test_plantcv_morphology_analyze_stem_bad_angle():
      # Clear previous outputs
      pcv.outputs.clear()
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_insertion_angle")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1)
      pruned, _, _ = pcv.morphology.prune(skel_img=skeleton, size=5)
      segmented_img, seg_objects = pcv.morphology.segment_skeleton(skel_img=pruned)
      _, _ = pcv.morphology.segment_sort(pruned, seg_objects)
      # print([stem_obj[3]])
      # stem_obj = [stem_obj[3]]
      stem_obj = [[[[1116, 1728]], [[1116, 1]]]]
      _ = pcv.morphology.analyze_stem(rgb_img=segmented_img, stem_objects=stem_obj)
      assert pcv.outputs.observations['default']['stem_angle']['value'] == 22877334.0
  
  
  # ########################################
  # Tests for the hyperspectral subpackage
  # ########################################
  def test_plantcv_hyperspectral_read_data_default():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_read_data_default")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = "plot"
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      _ = pcv.hyperspectral.read_data(filename=spectral_filename)
      pcv.params.debug = "print"
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      assert np.shape(array_data.array_data) == (1, 1600, 978)
  
  
  def test_plantcv_hyperspectral_read_data_no_default_bands():
      pcv.params.debug = "plot"
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA_NO_DEFAULT)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      assert np.shape(array_data.array_data) == (1, 1600, 978)
  
  
  def test_plantcv_hyperspectral_read_data_approx_pseudorgb():
      pcv.params.debug = "plot"
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA_APPROX_PSEUDO)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      assert np.shape(array_data.array_data) == (1, 1600, 978)
  
  
  def test_plantcv_hyperspectral_read_data_bad_interleave():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA_BAD_INTERLEAVE)
      with pytest.raises(RuntimeError):
          _ = pcv.hyperspectral.read_data(filename=spectral_filename)
  
  
  def test_plantcv_spectral_index_ndvi():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_ndvi")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.ndvi(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_ndvi_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.ndvi(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.ndvi(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_gdvi():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_gdvi")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.gdvi(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_gdvi_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.gdvi(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.gdvi(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_savi():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_savi")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.savi(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_savi_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.savi(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.savi(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_pri():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_pri")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.pri(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_pri_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.pri(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.pri(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_ari():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_ari")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.ari(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_ari_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.ari(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.ari(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_ci_rededge():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_ci_rededge")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.ci_rededge(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_ci_rededge_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.ci_rededge(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.ci_rededge(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_cri550():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_cri550")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.cri550(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_cri550_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.cri550(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.cri550(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_cri700():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_cri700")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.cri700(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_cri700_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.cri700(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.cri700(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_egi():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_egi")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      index_array = pcv.spectral_index.egi(rgb_img=rgb_img)
      assert np.shape(index_array.array_data) == (2056, 2454) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_evi():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_evi")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.evi(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_evi_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.evi(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.evi(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_mari():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_mari")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.mari(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_mari_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.mari(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.mari(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_mcari():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_mcari")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.mcari(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_mcari_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.mcari(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.mcari(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_mtci():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_mtci")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.mtci(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_mtci_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.mtci(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.mtci(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_ndre():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_ndre")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.ndre(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_ndre_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.ndre(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.ndre(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_psnd_chla():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_psnd_chla")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.psnd_chla(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_psnd_chla_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.psnd_chla(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.psnd_chla(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_psnd_chlb():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_psnd_chlb")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.psnd_chlb(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_psnd_chlb_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.psnd_chlb(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.psnd_chlb(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_psnd_car():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_psnd_car")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.psnd_car(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_psnd_car_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.psnd_car(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.psnd_car(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_psri():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_psri")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.psri(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_psri_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.psri(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.psri(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_pssr_chla():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_pssr_chla")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.pssr_chla(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_pssr_chla_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.pssr_chla(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.pssr_chla(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_pssr_chlb():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_pssr_chlb")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.pssr_chlb(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_pssr_chlb_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.pssr_chlb(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.pssr_chlb(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_pssr_car():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_pssr_car")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.pssr_car(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_pssr_car_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.pssr_car(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.pssr_car(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_rgri():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_rgri")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.rgri(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_rgri_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.rgri(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.rgri(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_rvsi():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_rvsi")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.rvsi(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_rvsi_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.rvsi(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.rvsi(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_sipi():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_sipi")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.sipi(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_sipi_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.sipi(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.sipi(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_sr():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_sr")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.sr(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_sr_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.sr(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.sr(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_vari():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_vari")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.vari(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_vari_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.vari(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.vari(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_vi_green():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_vi_green")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.vi_green(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_vi_green_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.vi_green(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.vi_green(hsi=index_array, distance=20)
  
  
  def test_plantcv_spectral_index_wi():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_wi")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.wi(hsi=array_data, distance=20)
      assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255
  
  
  def test_plantcv_spectral_index_wi_bad_input():
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      pcv.params.debug = None
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.wi(hsi=array_data, distance=20)
      with pytest.raises(RuntimeError):
          _ = pcv.spectral_index.wi(hsi=index_array, distance=20)
  
  
  def test_plantcv_hyperspectral_analyze_spectral():
      # Clear previous outputs
      pcv.outputs.clear()
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_analyze_spectral")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      mask = cv2.imread(os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_MASK), -1)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      # pcv.params.debug = "plot"
      # _ = pcv.hyperspectral.analyze_spectral(array=array_data, mask=mask, histplot=True)
      # pcv.params.debug = "print"
      # _ = pcv.hyperspectral.analyze_spectral(array=array_data, mask=mask, histplot=True, label="prefix")
      pcv.params.debug = None
      _ = pcv.hyperspectral.analyze_spectral(array=array_data, mask=mask, histplot=True, label="prefix")
      assert len(pcv.outputs.observations['prefix']['spectral_frequencies']['value']) == 978
  
  
  def test_plantcv_hyperspectral_analyze_index():
      # Clear previous outputs
      pcv.outputs.clear()
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_analyze_index")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.savi(hsi=array_data, distance=801)
      mask_img = np.ones(np.shape(index_array.array_data), dtype=np.uint8) * 255
      # pcv.params.debug = "print"
      # pcv.hyperspectral.analyze_index(index_array=index_array, mask=mask_img, histplot=True)
      # pcv.params.debug = "plot"
      # pcv.hyperspectral.analyze_index(index_array=index_array, mask=mask_img, histplot=True)
  
      pcv.params.debug = None
      pcv.hyperspectral.analyze_index(index_array=index_array, mask=mask_img, histplot=True)
  
      assert pcv.outputs.observations['default']['mean_index_savi']['value'] > 0
  
  
  def test_plantcv_hyperspectral_analyze_index_set_range():
      # Clear previous outputs
      pcv.outputs.clear()
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_analyze_index_set_range")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.savi(hsi=array_data, distance=801)
      mask_img = np.ones(np.shape(index_array.array_data), dtype=np.uint8) * 255
      pcv.params.debug = None
      pcv.hyperspectral.analyze_index(index_array=index_array, mask=mask_img, histplot=True, min_bin=0, max_bin=1)
      assert pcv.outputs.observations['default']['mean_index_savi']['value'] > 0
  
  
  def test_plantcv_hyperspectral_analyze_index_auto_range():
      # Clear previous outputs
      pcv.outputs.clear()
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_analyze_index_auto_range")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.savi(hsi=array_data, distance=801)
      mask_img = np.ones(np.shape(index_array.array_data), dtype=np.uint8) * 255
      pcv.params.debug = None
      pcv.hyperspectral.analyze_index(index_array=index_array, mask=mask_img, min_bin="auto", max_bin="auto")
      assert pcv.outputs.observations['default']['mean_index_savi']['value'] > 0
  
  
  def test_plantcv_hyperspectral_analyze_index_outside_range_warning():
      import io
      from contextlib import redirect_stdout
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_analyze_index_auto_range")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.savi(hsi=array_data, distance=801)
      mask_img = np.ones(np.shape(index_array.array_data), dtype=np.uint8) * 255
      f = io.StringIO()
      with redirect_stdout(f):
          pcv.params.debug = None
          pcv.hyperspectral.analyze_index(index_array=index_array, mask=mask_img, min_bin=.5, max_bin=.55, label="i")
      out = f.getvalue()
      # assert os.listdir(cache_dir) is 0
      assert out[0:10] == 'WARNING!!!'
  
  
  def test_plantcv_hyperspectral_analyze_index_bad_input_mask():
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.savi(hsi=array_data, distance=801)
      mask_img = cv2.imread(os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_MASK))
      with pytest.raises(RuntimeError):
          pcv.hyperspectral.analyze_index(index_array=index_array, mask=mask_img)
  
  
  def test_plantcv_hyperspectral_analyze_index_bad_input_index():
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      index_array = pcv.spectral_index.savi(hsi=array_data, distance=801)
      mask_img = cv2.imread(os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_MASK), -1)
      index_array.array_data = cv2.imread(os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_MASK))
      with pytest.raises(RuntimeError):
          pcv.hyperspectral.analyze_index(index_array=index_array, mask=mask_img)
  
  
  def test_plantcv_hyperspectral_analyze_index_bad_input_datatype():
      pcv.params.debug = None
      spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      array_data = pcv.hyperspectral.read_data(filename=spectral_filename)
      mask_img = cv2.imread(os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_MASK), -1)
      with pytest.raises(RuntimeError):
          pcv.hyperspectral.analyze_index(index_array=array_data, mask=mask_img)
  
  
  def test_plantcv_hyperspectral_calibrate():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_calibrate")
      os.mkdir(cache_dir)
      raw = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      white = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_WHITE)
      dark = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DARK)
      raw = pcv.hyperspectral.read_data(filename=raw)
      white = pcv.hyperspectral.read_data(filename=white)
      dark = pcv.hyperspectral.read_data(filename=dark)
      pcv.params.debug = "plot"
      _ = pcv.hyperspectral.calibrate(raw_data=raw, white_reference=white, dark_reference=dark)
      pcv.params.debug = "print"
      calibrated = pcv.hyperspectral.calibrate(raw_data=raw, white_reference=white, dark_reference=dark)
      assert np.shape(calibrated.array_data) == (1, 1600, 978)
  
  
  def test_plantcv_hyperspectral_extract_wavelength():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_extract_wavelength")
      os.mkdir(cache_dir)
      spectral = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      spectral = pcv.hyperspectral.read_data(filename=spectral)
      pcv.params.debug = "plot"
      _ = pcv.hyperspectral.extract_wavelength(spectral_data=spectral, wavelength=500)
      pcv.params.debug = "print"
      new = pcv.hyperspectral.extract_wavelength(spectral_data=spectral, wavelength=500)
      assert np.shape(new.array_data) == (1, 1600)
  
  
  def test_plantcv_hyperspectral_avg_reflectance():
      spectral = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      mask_img = cv2.imread(os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_MASK), -1)
      spectral = pcv.hyperspectral.read_data(filename=spectral)
      avg_reflect = pcv.hyperspectral._avg_reflectance(spectral, mask=mask_img)
      assert len(avg_reflect) == 978
  
  
  def test_plantcv_hyperspectral_inverse_covariance():
      spectral = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA)
      spectral = pcv.hyperspectral.read_data(filename=spectral)
      inv_cov = pcv.hyperspectral._inverse_covariance(spectral)
      assert np.shape(inv_cov) == (978, 978)
  
  
  # ########################################
  # Tests for the photosynthesis subpackage
  # ########################################
  def test_plantcv_photosynthesis_read_dat():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_photosynthesis_read_dat")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      pcv.params.debug = "plot"
      fluor_filename = os.path.join(FLUOR_TEST_DATA, FLUOR_IMG)
      _, _, _ = pcv.photosynthesis.read_cropreporter(filename=fluor_filename)
      pcv.params.debug = "print"
      fdark, fmin, fmax = pcv.photosynthesis.read_cropreporter(filename=fluor_filename)
      assert np.sum(fmin) < np.sum(fmax)
  
  
  def test_plantcv_photosynthesis_analyze_fvfm():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_fvfm")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # filename = os.path.join(cache_dir, 'plantcv_fvfm_hist.png')
      # Read in test data
      fdark = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FDARK), -1)
      fmin = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMIN), -1)
      fmax = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMAX), -1)
      fmask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMASK), -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.photosynthesis.analyze_fvfm(fdark=fdark, fmin=fmin, fmax=fmax, mask=fmask, bins=1000, label="prefix")
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      fvfm_images = pcv.photosynthesis.analyze_fvfm(fdark=fdark, fmin=fmin, fmax=fmax, mask=fmask, bins=1000)
      assert len(fvfm_images) != 0
  
  
  def test_plantcv_photosynthesis_analyze_fvfm_print_analysis_results():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_fvfm")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      fdark = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FDARK), -1)
      fmin = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMIN), -1)
      fmax = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMAX), -1)
      fmask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMASK), -1)
      _ = pcv.photosynthesis.analyze_fvfm(fdark=fdark, fmin=fmin, fmax=fmax, mask=fmask, bins=1000)
      result_file = os.path.join(cache_dir, "results.txt")
      pcv.print_results(result_file)
      pcv.outputs.clear()
      assert os.path.exists(result_file)
  
  
  def test_plantcv_photosynthesis_analyze_fvfm_bad_fdark():
      # Clear previous outputs
      pcv.outputs.clear()
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_fvfm")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      fdark = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FDARK), -1)
      fmin = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMIN), -1)
      fmax = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMAX), -1)
      fmask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMASK), -1)
      _ = pcv.photosynthesis.analyze_fvfm(fdark=fdark + 3000, fmin=fmin, fmax=fmax, mask=fmask, bins=1000)
      check = pcv.outputs.observations['default']['fdark_passed_qc']['value'] is False
      assert check
  
  
  def test_plantcv_photosynthesis_analyze_fvfm_bad_input():
      fdark = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      fmin = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMIN), -1)
      fmax = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMAX), -1)
      fmask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMASK), -1)
      with pytest.raises(RuntimeError):
          _ = pcv.photosynthesis.analyze_fvfm(fdark=fdark, fmin=fmin, fmax=fmax, mask=fmask, bins=1000)
  
  
  # ##############################
  # Tests for the roi subpackage
  # ##############################
  def test_plantcv_roi_from_binary_image():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_roi_from_binary_image")
      os.mkdir(cache_dir)
      # Read in test RGB image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # Create a binary image
      bin_img = np.zeros(np.shape(rgb_img)[0:2], dtype=np.uint8)
      cv2.rectangle(bin_img, (100, 100), (1000, 1000), 255, -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      pcv.params.debug_outdir = cache_dir
      _, _ = pcv.roi.from_binary_image(bin_img=bin_img, img=rgb_img)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _, _ = pcv.roi.from_binary_image(bin_img=bin_img, img=rgb_img)
      # Test with debug = None
      pcv.params.debug = None
      roi_contour, roi_hierarchy = pcv.roi.from_binary_image(bin_img=bin_img, img=rgb_img)
      # Assert the contours and hierarchy lists contain only the ROI
      assert np.shape(roi_contour) == (1, 3600, 1, 2)
  
  
  def test_plantcv_roi_from_binary_image_grayscale_input():
      # Read in a test grayscale image
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      # Create a binary image
      bin_img = np.zeros(np.shape(gray_img)[0:2], dtype=np.uint8)
      cv2.rectangle(bin_img, (100, 100), (1000, 1000), 255, -1)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      roi_contour, roi_hierarchy = pcv.roi.from_binary_image(bin_img=bin_img, img=gray_img)
      # Assert the contours and hierarchy lists contain only the ROI
      assert np.shape(roi_contour) == (1, 3600, 1, 2)
  
  
  def test_plantcv_roi_from_binary_image_bad_binary_input():
      # Read in test RGB image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # Binary input is required but an RGB input is provided
      with pytest.raises(RuntimeError):
          _, _ = pcv.roi.from_binary_image(bin_img=rgb_img, img=rgb_img)
  
  
  def test_plantcv_roi_rectangle():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_roi_rectangle")
      os.mkdir(cache_dir)
      # Read in test RGB image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # Test with debug = "print"
      pcv.params.debug = "print"
      pcv.params.debug_outdir = cache_dir
      _, _ = pcv.roi.rectangle(x=100, y=100, h=500, w=500, img=rgb_img)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _, _ = pcv.roi.rectangle(x=100, y=100, h=500, w=500, img=rgb_img)
      # Test with debug = None
      pcv.params.debug = None
      roi_contour, roi_hierarchy = pcv.roi.rectangle(x=100, y=100, h=500, w=500, img=rgb_img)
      # Assert the contours and hierarchy lists contain only the ROI
      assert np.shape(roi_contour) == (1, 4, 1, 2)
  
  
  def test_plantcv_roi_rectangle_grayscale_input():
      # Read in a test grayscale image
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      roi_contour, roi_hierarchy = pcv.roi.rectangle(x=100, y=100, h=500, w=500, img=gray_img)
      # Assert the contours and hierarchy lists contain only the ROI
      assert np.shape(roi_contour) == (1, 4, 1, 2)
  
  
  def test_plantcv_roi_rectangle_out_of_frame():
      # Read in test RGB image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # The resulting rectangle needs to be within the dimensions of the image
      with pytest.raises(RuntimeError):
          _, _ = pcv.roi.rectangle(x=100, y=100, h=500, w=3000, img=rgb_img)
  
  
  def test_plantcv_roi_circle():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_roi_circle")
      os.mkdir(cache_dir)
      # Read in test RGB image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # Test with debug = "print"
      pcv.params.debug = "print"
      pcv.params.debug_outdir = cache_dir
      _, _ = pcv.roi.circle(x=100, y=100, r=50, img=rgb_img)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _, _ = pcv.roi.circle(x=100, y=100, r=50, img=rgb_img)
      # Test with debug = None
      pcv.params.debug = None
      roi_contour, roi_hierarchy = pcv.roi.circle(x=200, y=225, r=75, img=rgb_img)
      # Assert the contours and hierarchy lists contain only the ROI
      assert np.shape(roi_contour) == (1, 424, 1, 2)
  
  
  def test_plantcv_roi_circle_grayscale_input():
      # Read in a test grayscale image
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      roi_contour, roi_hierarchy = pcv.roi.circle(x=200, y=225, r=75, img=gray_img)
      # Assert the contours and hierarchy lists contain only the ROI
      assert np.shape(roi_contour) == (1, 424, 1, 2)
  
  
  def test_plantcv_roi_circle_out_of_frame():
      # Read in test RGB image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # The resulting rectangle needs to be within the dimensions of the image
      with pytest.raises(RuntimeError):
          _, _ = pcv.roi.circle(x=50, y=225, r=75, img=rgb_img)
  
  
  def test_plantcv_roi_ellipse():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_roi_ellipse")
      os.mkdir(cache_dir)
      # Read in test RGB image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # Test with debug = "print"
      pcv.params.debug = "print"
      pcv.params.debug_outdir = cache_dir
      _, _ = pcv.roi.ellipse(x=200, y=200, r1=75, r2=50, angle=0, img=rgb_img)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _, _ = pcv.roi.ellipse(x=200, y=200, r1=75, r2=50, angle=0, img=rgb_img)
      # Test with debug = None
      pcv.params.debug = None
      roi_contour, roi_hierarchy = pcv.roi.ellipse(x=200, y=200, r1=75, r2=50, angle=0, img=rgb_img)
      # Assert the contours and hierarchy lists contain only the ROI
      assert np.shape(roi_contour) == (1, 360, 1, 2)
  
  
  def test_plantcv_roi_ellipse_grayscale_input():
      # Read in a test grayscale image
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      roi_contour, roi_hierarchy = pcv.roi.ellipse(x=200, y=200, r1=75, r2=50, angle=0, img=gray_img)
      # Assert the contours and hierarchy lists contain only the ROI
      assert np.shape(roi_contour) == (1, 360, 1, 2)
  
  
  def test_plantcv_roi_ellipse_out_of_frame():
      # Read in test RGB image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # The resulting rectangle needs to be within the dimensions of the image
      with pytest.raises(RuntimeError):
          _, _ = pcv.roi.ellipse(x=50, y=225, r1=75, r2=50, angle=0, img=rgb_img)
  
  
  def test_plantcv_roi_multi():
      # Read in test RGB image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.roi.multi(rgb_img, coord=[(25, 120), (100, 100)], radius=20)
      # Test with debug = None
      pcv.params.debug = None
      rois1, roi_hierarchy1 = pcv.roi.multi(rgb_img, coord=(25, 120), radius=20, spacing=(10, 10), nrows=3, ncols=6)
      # Assert the contours has 18 ROIs
      assert len(rois1) == 18
  
  
  def test_plantcv_roi_multi_bad_input():
      # Read in test RGB image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # The user must input a list of custom coordinates OR inputs to make a grid. Not both
      with pytest.raises(RuntimeError):
          _, _ = pcv.roi.multi(rgb_img, coord=[(25, 120), (100, 100)], radius=20, spacing=(10, 10), nrows=3, ncols=6)
  
  
  def test_plantcv_roi_multi_bad_input_oob():
      # Read in test RGB image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # nputs to make a grid make ROIs that go off the screen
      with pytest.raises(RuntimeError):
          _, _ = pcv.roi.multi(rgb_img, coord=(25000, 12000), radius=2, spacing=(1, 1), nrows=3, ncols=6)
  
  
  def test_plantcv_roi_multi_bad_input_oob_list():
      # Read in test RGB image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # All vertices in the list of centers must draw roi's that are inside the image
      with pytest.raises(RuntimeError):
          _, _ = pcv.roi.multi(rgb_img, coord=[(25000, 25000), (25000, 12000), (12000, 12000)], radius=20)
  
  
  def test_plantcv_roi_custom():
      # Read in test RGB image
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      pcv.params.debug = "plot"
      cnt, hier = pcv.roi.custom(img=img, vertices=[[226, 1], [313, 184], [240, 202], [220, 229], [161, 171]])
      assert np.shape(cnt) == (1, 5, 2)
  
  
  def test_plantcv_roi_custom_bad_input():
      # Read in test RGB image
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # ROI goes out of bounds
      with pytest.raises(RuntimeError):
          _ = pcv.roi.custom(img=img, vertices=[[226, -1], [3130, 1848], [2404, 2029], [2205, 2298], [1617, 1761]])
  
  
  # ##############################
  # Tests for the transform subpackage
  # ##############################
  def test_plantcv_transform_get_color_matrix():
      # load in target_matrix
      matrix_file = np.load(os.path.join(TEST_DATA, TEST_TARGET_MATRIX), encoding="latin1")
      matrix_compare = matrix_file['arr_0']
      # Read in rgb_img and gray-scale mask
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_MASK), -1)
      # The result should be a len(np.unique(mask))-1 x 4 matrix
      headers, matrix = pcv.transform.get_color_matrix(rgb_img, mask)
      assert np.array_equal(matrix, matrix_compare)
  
  
  def test_plantcv_transform_get_color_matrix_img():
      # Read in two gray-scale images
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_MASK), -1)
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_MASK), -1)
      # The input for rgb_img needs to be an RGB image
      with pytest.raises(RuntimeError):
          _, _ = pcv.transform.get_color_matrix(rgb_img, mask)
  
  
  def test_plantcv_transform_get_color_matrix_mask():
      # Read in two gray-scale images
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_MASK))
      # The input for rgb_img needs to be an RGB image
      with pytest.raises(RuntimeError):
          _, _ = pcv.transform.get_color_matrix(rgb_img, mask)
  
  
  def test_plantcv_transform_get_matrix_m():
      # load in comparison matrices
      matrix_m_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_M1), encoding="latin1")
      matrix_compare_m = matrix_m_file['arr_0']
      matrix_b_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_B1), encoding="latin1")
      matrix_compare_b = matrix_b_file['arr_0']
      # read in matrices
      t_matrix_file = np.load(os.path.join(TEST_DATA, TEST_TARGET_MATRIX), encoding="latin1")
      t_matrix = t_matrix_file['arr_0']
      s_matrix_file = np.load(os.path.join(TEST_DATA, TEST_SOURCE1_MATRIX), encoding="latin1")
      s_matrix = s_matrix_file['arr_0']
      # apply matrices to function
      matrix_a, matrix_m, matrix_b = pcv.transform.get_matrix_m(t_matrix, s_matrix)
      matrix_compare_m = np.rint(matrix_compare_m)
      matrix_compare_b = np.rint(matrix_compare_b)
      matrix_m = np.rint(matrix_m)
      matrix_b = np.rint(matrix_b)
      assert np.array_equal(matrix_m, matrix_compare_m) and np.array_equal(matrix_b, matrix_compare_b)
  
  
  def test_plantcv_transform_get_matrix_m_unequal_data():
      # load in comparison matrices
      matrix_m_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_M2), encoding="latin1")
      matrix_compare_m = matrix_m_file['arr_0']
      matrix_b_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_B2), encoding="latin1")
      matrix_compare_b = matrix_b_file['arr_0']
      # read in matrices
      t_matrix_file = np.load(os.path.join(TEST_DATA, TEST_TARGET_MATRIX), encoding="latin1")
      t_matrix = t_matrix_file['arr_0']
      s_matrix_file = np.load(os.path.join(TEST_DATA, TEST_SOURCE2_MATRIX), encoding="latin1")
      s_matrix = s_matrix_file['arr_0']
      # apply matrices to function
      matrix_a, matrix_m, matrix_b = pcv.transform.get_matrix_m(t_matrix, s_matrix)
      matrix_compare_m = np.rint(matrix_compare_m)
      matrix_compare_b = np.rint(matrix_compare_b)
      matrix_m = np.rint(matrix_m)
      matrix_b = np.rint(matrix_b)
      assert np.array_equal(matrix_m, matrix_compare_m) and np.array_equal(matrix_b, matrix_compare_b)
  
  
  def test_plantcv_transform_calc_transformation_matrix():
      # load in comparison matrices
      matrix_file = np.load(os.path.join(TEST_DATA, TEST_TRANSFORM1), encoding="latin1")
      matrix_compare = matrix_file['arr_0']
      # read in matrices
      matrix_m_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_M1), encoding="latin1")
      matrix_m = matrix_m_file['arr_0']
      matrix_b_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_B1), encoding="latin1")
      matrix_b = matrix_b_file['arr_0']
      # apply to function
      _, matrix_t = pcv.transform.calc_transformation_matrix(matrix_m, matrix_b)
      matrix_t = np.rint(matrix_t)
      matrix_compare = np.rint(matrix_compare)
      assert np.array_equal(matrix_t, matrix_compare)
  
  
  def test_plantcv_transform_calc_transformation_matrix_b_incorrect():
      # read in matrices
      matrix_m_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_M1), encoding="latin1")
      matrix_m = matrix_m_file['arr_0']
      matrix_b_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_B1), encoding="latin1")
      matrix_b = matrix_b_file['arr_0']
      matrix_b = np.asmatrix(matrix_b, float)
      with pytest.raises(RuntimeError):
          _, _ = pcv.transform.calc_transformation_matrix(matrix_m, matrix_b.T)
  
  
  def test_plantcv_transform_calc_transformation_matrix_not_mult():
      # read in matrices
      matrix_m_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_M1), encoding="latin1")
      matrix_m = matrix_m_file['arr_0']
      matrix_b_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_B1), encoding="latin1")
      matrix_b = matrix_b_file['arr_0']
      with pytest.raises(RuntimeError):
          _, _ = pcv.transform.calc_transformation_matrix(matrix_m, matrix_b[:3])
  
  
  def test_plantcv_transform_calc_transformation_matrix_not_mat():
      # read in matrices
      matrix_m_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_M1), encoding="latin1")
      matrix_m = matrix_m_file['arr_0']
      matrix_b_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_B1), encoding="latin1")
      matrix_b = matrix_b_file['arr_0']
      with pytest.raises(RuntimeError):
          _, _ = pcv.transform.calc_transformation_matrix(matrix_m[:, 1], matrix_b[:, 1])
  
  
  def test_plantcv_transform_apply_transformation():
      # load corrected image to compare
      corrected_compare = cv2.imread(os.path.join(TEST_DATA, TEST_S1_CORRECTED))
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform")
      os.mkdir(cache_dir)
      # Make image and mask directories in the cache directory
      imgdir = os.path.join(cache_dir, "images")
      # read in matrices
      matrix_t_file = np.load(os.path.join(TEST_DATA, TEST_TRANSFORM1), encoding="latin1")
      matrix_t = matrix_t_file['arr_0']
      # read in images
      target_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG))
      source_img = cv2.imread(os.path.join(TEST_DATA, TEST_SOURCE1_IMG))
      # Test with debug = "print"
      pcv.params.debug = "print"
      pcv.params.debug_outdir = imgdir
      _ = pcv.transform.apply_transformation_matrix(source_img, target_img, matrix_t)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.transform.apply_transformation_matrix(source_img, target_img, matrix_t)
      # Test with debug = None
      pcv.params.debug = None
      corrected_img = pcv.transform.apply_transformation_matrix(source_img, target_img, matrix_t)
      # assert source and corrected have same shape
      assert np.array_equal(corrected_img, corrected_compare)
  
  
  def test_plantcv_transform_apply_transformation_incorrect_t():
      # read in matrices
      matrix_t_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_B1), encoding="latin1")
      matrix_t = matrix_t_file['arr_0']
      # read in images
      target_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG))
      source_img = cv2.imread(os.path.join(TEST_DATA, TEST_SOURCE1_IMG))
      with pytest.raises(RuntimeError):
          _ = pcv.transform.apply_transformation_matrix(source_img, target_img, matrix_t)
  
  
  def test_plantcv_transform_apply_transformation_incorrect_img():
      # read in matrices
      matrix_t_file = np.load(os.path.join(TEST_DATA, TEST_TRANSFORM1), encoding="latin1")
      matrix_t = matrix_t_file['arr_0']
      # read in images
      target_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG))
      source_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_MASK), -1)
      with pytest.raises(RuntimeError):
          _ = pcv.transform.apply_transformation_matrix(source_img, target_img, matrix_t)
  
  
  def test_plantcv_transform_save_matrix():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform")
      os.mkdir(cache_dir)
      # read in matrix
      matrix_t_file = np.load(os.path.join(TEST_DATA, TEST_TRANSFORM1), encoding="latin1")
      matrix_t = matrix_t_file['arr_0']
      # .npz filename
      filename = os.path.join(cache_dir, 'test.npz')
      pcv.transform.save_matrix(matrix_t, filename)
      assert os.path.exists(filename) is True
  
  
  def test_plantcv_transform_save_matrix_incorrect_filename():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform")
      os.mkdir(cache_dir)
      # read in matrix
      matrix_t_file = np.load(os.path.join(TEST_DATA, TEST_TRANSFORM1), encoding="latin1")
      matrix_t = matrix_t_file['arr_0']
      # .npz filename
      filename = "test"
      with pytest.raises(RuntimeError):
          pcv.transform.save_matrix(matrix_t, filename)
  
  
  def test_plantcv_transform_load_matrix():
      # read in matrix_t
      matrix_t_file = np.load(os.path.join(TEST_DATA, TEST_TRANSFORM1), encoding="latin1")
      matrix_t = matrix_t_file['arr_0']
      # test load function with matrix_t
      matrix_t_loaded = pcv.transform.load_matrix(os.path.join(TEST_DATA, TEST_TRANSFORM1))
      assert np.array_equal(matrix_t, matrix_t_loaded)
  
  
  def test_plantcv_transform_correct_color():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform")
      os.mkdir(cache_dir)
      # load corrected image to compare
      corrected_compare = cv2.imread(os.path.join(TEST_DATA, TEST_S1_CORRECTED))
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_correct_color")
      os.mkdir(cache_dir)
      # Make image and mask directories in the cache directory
      imgdir = os.path.join(cache_dir, "images")
      matdir = os.path.join(cache_dir, "saved_matrices")
      # Read in target, source, and gray-scale mask
      target_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG))
      source_img = cv2.imread(os.path.join(TEST_DATA, TEST_SOURCE1_IMG))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_MASK), -1)
      output_path = os.path.join(matdir)
      # Test with debug = "print"
      pcv.params.debug = "print"
      pcv.params.debug_outdir = imgdir
      _, _, _, _ = pcv.transform.correct_color(target_img, mask, source_img, mask, cache_dir)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _, _, _, _ = pcv.transform.correct_color(target_img, mask, source_img, mask, output_path)
      # Test with debug = None
      pcv.params.debug = None
      _, _, matrix_t, corrected_img = pcv.transform.correct_color(target_img, mask, source_img, mask, output_path)
      # assert source and corrected have same shape
      assert all([np.array_equal(corrected_img, corrected_compare),
                  os.path.exists(os.path.join(output_path, "target_matrix.npz")) is True,
                  os.path.exists(os.path.join(output_path, "source_matrix.npz")) is True,
                  os.path.exists(os.path.join(output_path, "transformation_matrix.npz")) is True])
  
  
  def test_plantcv_transform_correct_color_output_dne():
      # load corrected image to compare
      corrected_compare = cv2.imread(os.path.join(TEST_DATA, TEST_S1_CORRECTED))
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_correct_color_output_dne")
      os.mkdir(cache_dir)
      # Make image and mask directories in the cache directory
      imgdir = os.path.join(cache_dir, "images")
      # Read in target, source, and gray-scale mask
      target_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG))
      source_img = cv2.imread(os.path.join(TEST_DATA, TEST_SOURCE1_IMG))
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_MASK), -1)
      output_path = os.path.join(cache_dir, "saved_matrices_1")  # output_directory that does not currently exist
      # Test with debug = "print"
      pcv.params.debug = "print"
      pcv.params.debug_outdir = imgdir
      _, _, _, _ = pcv.transform.correct_color(target_img, mask, source_img, mask, output_path)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _, _, _, _ = pcv.transform.correct_color(target_img, mask, source_img, mask, output_path)
      # Test with debug = None
      pcv.params.debug = None
      _, _, matrix_t, corrected_img = pcv.transform.correct_color(target_img, mask, source_img, mask, output_path)
      # assert source and corrected have same shape
      assert all([np.array_equal(corrected_img, corrected_compare),
                  os.path.exists(os.path.join(output_path, "target_matrix.npz")) is True,
                  os.path.exists(os.path.join(output_path, "source_matrix.npz")) is True,
                  os.path.exists(os.path.join(output_path, "transformation_matrix.npz")) is True])
  
  
  def test_plantcv_transform_create_color_card_mask():
      # Load target image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG))
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_create_color_card_mask")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.transform.create_color_card_mask(rgb_img=rgb_img, radius=6, start_coord=(166, 166),
                                               spacing=(21, 21), nrows=6, ncols=4, exclude=[20, 0])
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.transform.create_color_card_mask(rgb_img=rgb_img, radius=6, start_coord=(166, 166),
                                               spacing=(21, 21), nrows=6, ncols=4, exclude=[20, 0])
      # Test with debug = None
      pcv.params.debug = None
      mask = pcv.transform.create_color_card_mask(rgb_img=rgb_img, radius=6, start_coord=(166, 166),
                                                  spacing=(21, 21), nrows=6, ncols=4, exclude=[20, 0])
      assert all([i == j] for i, j in zip(np.unique(mask), np.array([0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110,
                                                                     120, 130, 140, 150, 160, 170, 180, 190, 200, 210,
                                                                     220], dtype=np.uint8)))
  
  
  def test_plantcv_transform_quick_color_check():
      # Load target image
      t_matrix = np.load(os.path.join(TEST_DATA, TEST_TARGET_MATRIX), encoding="latin1")
      target_matrix = t_matrix['arr_0']
      s_matrix = np.load(os.path.join(TEST_DATA, TEST_SOURCE1_MATRIX), encoding="latin1")
      source_matrix = s_matrix['arr_0']
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_quick_color_check")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Test with debug = "print"
      pcv.params.debug = "print"
      pcv.transform.quick_color_check(target_matrix, source_matrix, num_chips=22)
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      pcv.transform.quick_color_check(target_matrix, source_matrix, num_chips=22)
      # Test with debug = None
      pcv.params.debug = None
      pcv.transform.quick_color_check(target_matrix, source_matrix, num_chips=22)
      assert os.path.exists(os.path.join(cache_dir, "color_quick_check.png"))
  
  
  def test_plantcv_transform_find_color_card():
      # Load rgb image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG))
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_find_color_card")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      df, start, space = pcv.transform.find_color_card(rgb_img=rgb_img, threshold_type='adaptgauss', blurry=False,
                                                       threshvalue=90)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.transform.create_color_card_mask(rgb_img=rgb_img, radius=6, start_coord=start,
                                               spacing=space, nrows=6, ncols=4, exclude=[20, 0])
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.transform.create_color_card_mask(rgb_img=rgb_img, radius=6, start_coord=start,
                                               spacing=space, nrows=6, ncols=4, exclude=[20, 0])
      # Test with debug = None
      pcv.params.debug = None
      mask = pcv.transform.create_color_card_mask(rgb_img=rgb_img, radius=6, start_coord=start,
                                                  spacing=space, nrows=6, ncols=4, exclude=[20, 0])
      assert all([i == j] for i, j in zip(np.unique(mask), np.array([0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110,
                                                                     120, 130, 140, 150, 160, 170, 180, 190, 200, 210,
                                                                     220], dtype=np.uint8)))
  
  
  def test_plantcv_transform_find_color_card_optional_parameters():
      # Clear previous outputs
      pcv.outputs.clear()
      # Load rgb image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG_COLOR_CARD))
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_find_color_card")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Test with threshold ='normal'
      df1, start1, space1 = pcv.transform.find_color_card(rgb_img=rgb_img, threshold_type='normal', blurry=True,
                                                          background='light', threshvalue=90, label="prefix")
      assert pcv.outputs.observations["prefix"]["color_chip_size"]["value"] > 15000
  
  
  def test_plantcv_transform_find_color_card_otsu():
      # Clear previous outputs
      pcv.outputs.clear()
      # Load rgb image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG_COLOR_CARD))
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_find_color_card_otsu")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Test with threshold ='normal'
      df1, start1, space1 = pcv.transform.find_color_card(rgb_img=rgb_img, threshold_type='otsu', blurry=True,
                                                          background='light', threshvalue=90, label="prefix")
      assert pcv.outputs.observations["prefix"]["color_chip_size"]["value"] > 15000
  
  
  def test_plantcv_transform_find_color_card_optional_size_parameters():
      # Clear previous outputs
      pcv.outputs.clear()
      # Load rgb image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG_COLOR_CARD))
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_find_color_card")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      _, _, _ = pcv.transform.find_color_card(rgb_img=rgb_img, record_chip_size="mean")
      assert pcv.outputs.observations["default"]["color_chip_size"]["value"] > 15000
  
  
  def test_plantcv_transform_find_color_card_optional_size_parameters_none():
      # Clear previous outputs
      pcv.outputs.clear()
      # Load rgb image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG_COLOR_CARD))
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_find_color_card")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      _, _, _ = pcv.transform.find_color_card(rgb_img=rgb_img, record_chip_size=None)
      assert pcv.outputs.observations.get("default") is None
  
  
  def test_plantcv_transform_find_color_card_bad_record_chip_size():
      # Clear previous outputs
      pcv.outputs.clear()
      # Load rgb image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG))
      pcv.params.debug = None
      _, _, _ = pcv.transform.find_color_card(rgb_img=rgb_img, record_chip_size='averageeeed')
      assert pcv.outputs.observations["default"]["color_chip_size"]["value"] is None
  
  
  def test_plantcv_transform_find_color_card_bad_thresh_input():
      # Load rgb image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG))
      with pytest.raises(RuntimeError):
          pcv.params.debug = None
          _, _, _ = pcv.transform.find_color_card(rgb_img=rgb_img, threshold_type='gaussian')
  
  
  def test_plantcv_transform_find_color_card_bad_background_input():
      # Load rgb image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG))
      with pytest.raises(RuntimeError):
          pcv.params.debug = None
          _, _, _ = pcv.transform.find_color_card(rgb_img=rgb_img, background='lite')
  
  
  def test_plantcv_transform_find_color_card_bad_colorcard():
      # Load rgb image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG_WITH_HEXAGON))
      with pytest.raises(RuntimeError):
          pcv.params.debug = None
          _, _, _ = pcv.transform.find_color_card(rgb_img=rgb_img)
  
  
  def test_plantcv_transform_rescale():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_rescale")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      # Test with debug = "print"
      pcv.params.debug = "print"
      _ = pcv.transform.rescale(gray_img=gray_img, min_value=0, max_value=100)
      pcv.params.debug = "plot"
      rescaled_img = pcv.transform.rescale(gray_img=gray_img, min_value=0, max_value=100)
      assert max(np.unique(rescaled_img)) == 100
  
  
  def test_plantcv_transform_rescale_bad_input():
      # Load rgb image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG))
      with pytest.raises(RuntimeError):
          _ = pcv.transform.rescale(gray_img=rgb_img)
  
  
  def test_plantcv_transform_resize():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_trancform_resize")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY_SMALL), -1)
      size = (100, 100)
      # Test with debug "print"
      pcv.params.debug = "print"
      _ = pcv.transform.resize(img=gray_img, size=size, interpolation="auto")
      # Test with debug "plot"
      pcv.params.debug = "plot"
      resized_img = pcv.transform.resize(img=gray_img, size=size, interpolation="auto")
      assert resized_img.shape == size
  
  
  def test_plantcv_transform_resize_unsupported_method():
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY_SMALL), -1)
      with pytest.raises(RuntimeError):
          _ = pcv.transform.resize(img=gray_img, size=(100, 100), interpolation="mymethod")
  
  
  def test_plantcv_transform_resize_crop():
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY_SMALL), -1)
      size = (20, 20)
      resized_im = pcv.transform.resize(img=gray_img, size=size, interpolation=None)
      assert resized_im.shape == size
  
  
  def test_plantcv_transform_resize_pad():
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY_SMALL), -1)
      size = (100, 100)
      resized_im = pcv.transform.resize(img=gray_img, size=size, interpolation=None)
      assert resized_im.shape == size
  
  
  def test_plantcv_transform_resize_pad_crop_color():
      color_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY_SMALL))
      size = (100, 100)
      resized_im = pcv.transform.resize(img=color_img, size=size, interpolation=None)
      assert resized_im.shape == (size[1], size[0], 3)
  
  
  def test_plantcv_transform_resize_factor():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_trancform_resize_factor")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY_SMALL), -1)
      # Resizing factors
      factor_x = 0.5
      factor_y = 0.2
      # Test with debug "print"
      pcv.params.debug = "print"
      _ = pcv.transform.resize_factor(img=gray_img, factors=(factor_x, factor_y), interpolation="auto")
      # Test with debug "plot"
      pcv.params.debug = "plot"
      resized_img = pcv.transform.resize_factor(img=gray_img, factors=(factor_x, factor_y), interpolation="auto")
      output_size = resized_img.shape
      expected_size = (int(gray_img.shape[0] * factor_y), int(gray_img.shape[1] * factor_x))
      assert output_size == expected_size
  
  
  def test_plantcv_transform_resize_factor_bad_input():
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY_SMALL), -1)
      with pytest.raises(RuntimeError):
          _ = pcv.transform.resize_factor(img=gray_img, factors=(0, 2), interpolation="auto")
  
  
  def test_plantcv_transform_nonuniform_illumination_rgb():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_nonuniform_illumination")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Load rgb image
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG))
      pcv.params.debug = "plot"
      _ = pcv.transform.nonuniform_illumination(img=rgb_img, ksize=11)
      pcv.params.debug = "print"
      corrected = pcv.transform.nonuniform_illumination(img=rgb_img, ksize=11)
      assert np.mean(corrected) < np.mean(rgb_img)
  
  
  def test_plantcv_transform_nonuniform_illumination_gray():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_nonuniform_illumination")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Load rgb image
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      pcv.params.debug = "plot"
      _ = pcv.transform.nonuniform_illumination(img=gray_img, ksize=11)
      pcv.params.debug = "print"
      corrected = pcv.transform.nonuniform_illumination(img=gray_img, ksize=11)
      assert np.shape(corrected) == np.shape(gray_img)
  
  
  def test_plantcv_transform_warp_default():
      pcv.params.debug = "plot"
      img = create_test_img((12, 10, 3))
      refimg = create_test_img((12, 10, 3))
      pts = [(0, 0),(1, 0),(0, 3),(4, 4)]
      refpts = [(0, 0),(1, 0),(0, 3),(4, 4)]
      warped_img, mat = pcv.transform.warp(img, refimg, pts, refpts, method="default")
      assert mat.shape == (3, 3)
  
  
  def test_plantcv_transform_warp_lmeds():
      pcv.params.debug = "plot"
      img = create_test_img((10, 10, 3))
      refimg = create_test_img((11, 11))
      pts = [(0, 0), (1, 0), (0, 3), (4, 4)]
      refpts = [(0, 0), (1, 0), (0, 3), (4, 4)]
      warped_img, mat = pcv.transform.warp(img, refimg, pts, refpts, method="lmeds")
      assert mat.shape == (3, 3)
  
  
  def test_plantcv_transform_warp_rho():
      pcv.params.debug = "plot"
      img = create_test_img_bin((10, 10))
      refimg = create_test_img((11, 11))
      pts = [(0, 0), (1, 0), (0, 3), (4, 4)]
      refpts = [(0, 0), (1, 0), (0, 3), (4, 4)]
      warped_img, mat = pcv.transform.warp(img, refimg, pts, refpts, method="rho")
      assert mat.shape == (3, 3)
  
  
  def test_plantcv_transform_warp_ransac():
      pcv.params.debug = "plot"
      img = create_test_img((100, 150))
      refimg = create_test_img((10, 15))
      pts = [(0, 0), (149, 0), (99, 149), (0, 99), (3, 3)]
      refpts = [(0, 0), (0, 14), (9, 14), (0, 9), (3, 3)]
      warped_img, mat = pcv.transform.warp(img, refimg, pts, refpts, method="ransac")
      assert mat.shape == (3, 3)
  
  
  @pytest.mark.parametrize("pts, refpts", [
      [[(0,0)],[(0,0),(0,1)]],  # different # of points provided for img and refimg
      [[(0,0)],[(0,0)]],  # not enough pairs of points provided
      [[(0, 0), (0, 14), (9, 14), (0, 9), (3, 3)],
       [(0, 0), (149, 0), (99, 149), (0, 99), (3, 3)]]  # homography not able to be calculated (cannot converge)
  ])
  def test_plantcv_transform_warp_err(pts, refpts):
      img = create_test_img((10, 15))
      refimg = create_test_img((100, 150))
      method = "rho"
      with pytest.raises(RuntimeError):
          pcv.transform.warp(img, refimg, pts, refpts, method=method)
  
  
  def test_plantcv_transform_warp_align():
      img = create_test_img((10, 10, 3))
      refimg = create_test_img((11, 11))
      mat = np.array([[ 1.00000000e+00,  1.04238500e-15, -7.69185075e-16],
                      [ 1.44375646e-16,  1.00000000e+00,  0.00000000e+00],
                      [-5.41315251e-16,  1.78930521e-15,  1.00000000e+00]])
      warp_img = pcv.transform.warp_align(img=img, mat=mat, refimg=refimg)
      assert warp_img.shape == (11, 11, 3)
  
  
  # ##############################
  # Tests for the threshold subpackage
  # ##############################
  @pytest.mark.parametrize("objtype", ["dark", "light"])
  def test_plantcv_threshold_binary(objtype):
      # Read in test data
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      # Test with object type = dark
      pcv.params.debug = None
      binary_img = pcv.threshold.binary(gray_img=gray_img, threshold=25, max_value=255, object_type=objtype)
      # Assert that the output image has the dimensions of the input image
      if all([i == j] for i, j in zip(np.shape(binary_img), TEST_GRAY_DIM)):
          # Assert that the image is binary
          if all([i == j] for i, j in zip(np.unique(binary_img), [0, 255])):
              assert 1
          else:
              assert 0
      else:
          assert 0
  
  
  def test_plantcv_threshold_binary_incorrect_object_type():
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      with pytest.raises(RuntimeError):
          pcv.params.debug = None
          _ = pcv.threshold.binary(gray_img=gray_img, threshold=25, max_value=255, object_type="lite")
  
  
  @pytest.mark.parametrize("objtype", ["dark", "light"])
  def test_plantcv_threshold_gaussian(objtype):
      # Read in test data
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      # Test with object type = dark
      pcv.params.debug = None
      binary_img = pcv.threshold.gaussian(gray_img=gray_img, max_value=255, object_type=objtype)
      # Assert that the output image has the dimensions of the input image
      if all([i == j] for i, j in zip(np.shape(binary_img), TEST_GRAY_DIM)):
          # Assert that the image is binary
          if all([i == j] for i, j in zip(np.unique(binary_img), [0, 255])):
              assert 1
          else:
              assert 0
      else:
          assert 0
  
  
  def test_plantcv_threshold_gaussian_incorrect_object_type():
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      with pytest.raises(RuntimeError):
          pcv.params.debug = None
          _ = pcv.threshold.gaussian(gray_img=gray_img, max_value=255, object_type="lite")
  
  
  @pytest.mark.parametrize("objtype", ["dark", "light"])
  def test_plantcv_threshold_mean(objtype):
      # Read in test data
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      # Test with object type = dark
      pcv.params.debug = None
      binary_img = pcv.threshold.mean(gray_img=gray_img, max_value=255, object_type=objtype)
      # Assert that the output image has the dimensions of the input image
      if all([i == j] for i, j in zip(np.shape(binary_img), TEST_GRAY_DIM)):
          # Assert that the image is binary
          if all([i == j] for i, j in zip(np.unique(binary_img), [0, 255])):
              assert 1
          else:
              assert 0
      else:
          assert 0
  
  
  def test_plantcv_threshold_mean_incorrect_object_type():
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      with pytest.raises(RuntimeError):
          pcv.params.debug = None
          _ = pcv.threshold.mean(gray_img=gray_img, max_value=255, object_type="lite")
  
  
  @pytest.mark.parametrize("objtype", ["dark", "light"])
  def test_plantcv_threshold_otsu(objtype):
      # Read in test data
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GREENMAG), -1)
      # Test with object set to light
      pcv.params.debug = None
      binary_img = pcv.threshold.otsu(gray_img=gray_img, max_value=255, object_type=objtype)
      # Assert that the output image has the dimensions of the input image
      if all([i == j] for i, j in zip(np.shape(binary_img), TEST_GRAY_DIM)):
          # Assert that the image is binary
          if all([i == j] for i, j in zip(np.unique(binary_img), [0, 255])):
              assert 1
          else:
              assert 0
      else:
          assert 0
  
  
  def test_plantcv_threshold_otsu_incorrect_object_type():
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      with pytest.raises(RuntimeError):
          pcv.params.debug = None
          _ = pcv.threshold.otsu(gray_img=gray_img, max_value=255, object_type="lite")
  
  
  @pytest.mark.parametrize("channel,lower_thresh,upper_thresh", [["HSV", [0, 0, 0], [255, 255, 255]],
                                                                 ["LAB", [0, 0, 0], [255, 255, 255]],
                                                                 ["RGB", [0, 0, 0], [255, 255, 255]],
                                                                 ["GRAY", [0], [255]]])
  def test_plantcv_threshold_custom_range_rgb(channel, lower_thresh, upper_thresh):
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # Test with debug = None
      pcv.params.debug = None
      mask, binary_img = pcv.threshold.custom_range(img, lower_thresh=lower_thresh, upper_thresh=upper_thresh,
                                                    channel=channel)
      # Assert that the output image has the dimensions of the input image
      if all([i == j] for i, j in zip(np.shape(binary_img), TEST_GRAY_DIM)):
          # Assert that the image is binary
          if all([i == j] for i, j in zip(np.unique(binary_img), [0, 255])):
              assert 1
          else:
              assert 0
      else:
          assert 0
  
  
  def test_plantcv_threshold_custom_range_grayscale():
      # Read in test data
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      # Test with debug = None
      pcv.params.debug = None
      # # Test channel='gray'
      mask, binary_img = pcv.threshold.custom_range(gray_img, lower_thresh=[0], upper_thresh=[255], channel='gray')
      # Assert that the output image has the dimensions of the input image
      if all([i == j] for i, j in zip(np.shape(binary_img), TEST_GRAY_DIM)):
          # Assert that the image is binary
          if all([i == j] for i, j in zip(np.unique(binary_img), [0, 255])):
              assert 1
          else:
              assert 0
      else:
          assert 0
  
  
  def test_plantcv_threshold_custom_range_bad_input_hsv():
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      with pytest.raises(RuntimeError):
          _, _ = pcv.threshold.custom_range(img, lower_thresh=[0, 0], upper_thresh=[2, 2, 2, 2], channel='HSV')
  
  
  def test_plantcv_threshold_custom_range_bad_input_rgb():
      # Read in test data
      pcv.params.debug = None
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      with pytest.raises(RuntimeError):
          _, _ = pcv.threshold.custom_range(img, lower_thresh=[0, 0], upper_thresh=[2, 2, 2, 2], channel='RGB')
  
  
  def test_plantcv_threshold_custom_range_bad_input_lab():
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      with pytest.raises(RuntimeError):
          _, _ = pcv.threshold.custom_range(img, lower_thresh=[0, 0], upper_thresh=[2, 2, 2], channel='LAB')
  
  
  def test_plantcv_threshold_custom_range_bad_input_gray():
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      with pytest.raises(RuntimeError):
          _, _ = pcv.threshold.custom_range(img, lower_thresh=[0, 0], upper_thresh=[2], channel='gray')
  
  
  def test_plantcv_threshold_custom_range_bad_input_channel():
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      with pytest.raises(RuntimeError):
          _, _ = pcv.threshold.custom_range(img, lower_thresh=[0], upper_thresh=[2], channel='CMYK')
  
  
  @pytest.mark.parametrize("channel", ["all", "any"])
  def test_plantcv_threshold_saturation(channel):
      # Read in test data
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # Test with debug = None
      pcv.params.debug = None
      thresh = pcv.threshold.saturation(rgb_img=rgb_img, threshold=254, channel=channel)
      assert len(np.unique(thresh)) == 2
  
  
  def test_plantcv_threshold_saturation_bad_input():
      # Read in test data
      rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      with pytest.raises(RuntimeError):
          _ = pcv.threshold.saturation(rgb_img=rgb_img, threshold=254, channel="red")
  
  
  def test_plantcv_threshold_triangle():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_triangle")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
  
      pcv.params.debug = None
      _ = pcv.threshold.triangle(gray_img=gray_img, max_value=255, object_type="dark", xstep=10)
      pcv.params.debug = "plot"
      _ = pcv.threshold.triangle(gray_img=gray_img, max_value=255, object_type="light", xstep=10)
      pcv.params.debug = "print"
      binary_img = pcv.threshold.triangle(gray_img=gray_img, max_value=255, object_type="light", xstep=10)
      # Assert that the output image has the dimensions of the input image
      if all([i == j] for i, j in zip(np.shape(binary_img), TEST_GRAY_DIM)):
          # Assert that the image is binary
          if all([i == j] for i, j in zip(np.unique(binary_img), [0, 255])):
              assert 1
          else:
              assert 0
      else:
          assert 0
  
  
  def test_plantcv_threshold_triangle_incorrect_object_type():
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      with pytest.raises(RuntimeError):
          pcv.params.debug = None
          _ = pcv.threshold.triangle(gray_img=gray_img, max_value=255, object_type="lite", xstep=10)
  
  
  def test_plantcv_threshold_texture():
      # Test with debug = None
      pcv.params.debug = None
      gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY_SMALL), -1)
      binary_img = pcv.threshold.texture(gray_img, ksize=6, threshold=7, offset=3, texture_method='dissimilarity',
                                         borders='nearest', max_value=255)
      # Assert that the output image has the dimensions of the input image
      if all([i == j] for i, j in zip(np.shape(binary_img), TEST_GRAY_DIM)):
          # Assert that the image is binary
          if all([i == j] for i, j in zip(np.unique(binary_img), [0, 255])):
              assert 1
          else:
              assert 0
      else:
          assert 0
  
  
  def create_test_img(sz_img):
      img = np.random.randint(np.prod(sz_img), size=sz_img) * 255
      img = img.astype(np.uint8)
      return img
  
  
  def create_test_img_bin(sz_img):
      img = np.zeros(sz_img)
      img[3:7, 2:8] = 1
      return img
  
  
  @pytest.mark.parametrize("bad_type", ["native", "nan", "inf"])
  def test_plantcv_threshold_mask_bad(bad_type):
      # Create a synthetic bad image
      bad_img = np.reshape(np.random.rand(25), (5, 5))
      bad_img[2, 2] = np.inf
      bad_img[2, 3] = np.nan
      sz = np.shape(bad_img)
      pcv.params.debug = None
      mask = pcv.threshold.mask_bad(bad_img, bad_type=bad_type)
      assert((np.shape(mask) == sz) and (len(np.unique(mask)) == 2))
  
  
  def test_plantcv_threshold_mask_bad_native_bad_input():
      # Create a synthetic bad image
      bad_img = np.reshape(np.random.rand(25), (5, 5))
      sz = np.shape(bad_img)
      mask10 = pcv.threshold.mask_bad(bad_img, bad_type='native')
  
      assert mask10.all() == np.zeros(sz, dtype='uint8').all()
  
  
  def test_plantcv_threshold_mask_bad_nan_bad_input():
      # Create a synthetic bad image
      bad_img = np.reshape(np.random.rand(25), (5, 5))
      bad_img[2, 2] = np.inf
      sz = np.shape(bad_img)
      mask11 = pcv.threshold.mask_bad(bad_img, bad_type='nan')
  
      assert mask11.all() == np.zeros(sz, dtype='uint8').all()
  
  def test_plantcv_threshold_mask_bad_input_color_img():
      # Read in test data
      bad_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      with pytest.raises(RuntimeError):
          pcv.threshold.mask_bad(bad_img, bad_type='nan')
  
  
  # ###################################
  # Tests for the visualize subpackage
  # ###################################
  def test_plantcv_visualize_auto_threshold_methods_bad_input():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_auto_threshold_methods")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      with pytest.raises(RuntimeError):
          _ = pcv.visualize.auto_threshold_methods(gray_img=img)
  
  
  def test_plantcv_visualize_auto_threshold_methods():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_auto_threshold_methods")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      pcv.params.debug = "print"
      _ = pcv.visualize.auto_threshold_methods(gray_img=img)
      pcv.params.debug = "plot"
      labeled_imgs = pcv.visualize.auto_threshold_methods(gray_img=img)
      assert len(labeled_imgs) == 5 and np.shape(labeled_imgs[0])[0] == np.shape(img)[0]
  
  
  @pytest.mark.parametrize("debug,axes", [["print", True], ["plot", False]])
  def test_plantcv_visualize_pseudocolor(debug, axes, tmpdir):
      # Create a tmp directory
      cache_dir = tmpdir.mkdir("sub")
      pcv.params.debug_outdir = cache_dir
      # Input image
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      r, c = img.shape
      # generate 200 "bad" pixels
      mask_bad = np.zeros((r, c), dtype=np.uint8)
      mask_bad = np.reshape(mask_bad, (-1, 1))
      mask_bad[0:100] = 255
      mask_bad = np.reshape(mask_bad, (r, c))
      # Debug mode
      pcv.params.debug = debug
      pseudo_img = pcv.visualize.pseudocolor(gray_img=img, mask=None, title="Pseudocolored image", axes=axes,
                                             bad_mask=mask_bad)
      # Assert that the output image has the dimensions of the input image
      assert all([i == j] for i, j in zip(np.shape(pseudo_img), TEST_BINARY_DIM))
  
  
  @pytest.mark.parametrize("bkgrd,axes,pad", [["image", True, "auto"], ["white", False, 1], ["black", True, "auto"]])
  def test_plantcv_visualize_pseudocolor_mask(bkgrd, axes, pad):
      # Test with debug = None
      pcv.params.debug = None
      # Input image
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      # Input mask
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      # Input contours
      contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1")
      obj_contour = contours_npz['arr_0']
      r, c = img.shape
      # generate 200 "bad" pixels
      mask_bad = np.zeros((r, c), dtype=np.uint8)
      mask_bad = np.reshape(mask_bad, (-1, 1))
      mask_bad[0:100] = 255
      mask_bad = np.reshape(mask_bad, (r, c))
      pseudo_img = pcv.visualize.pseudocolor(gray_img=img, obj=obj_contour, mask=mask, background=bkgrd,
                                             bad_mask=mask_bad, title="Pseudocolored image", axes=axes, obj_padding=pad)
      # Assert that the output image has the dimensions of the input image
      if all([i == j] for i, j in zip(np.shape(pseudo_img), TEST_BINARY_DIM)):
          assert 1
      else:
          assert 0
  
  
  def test_plantcv_visualize_pseudocolor_bad_input():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_pseudocolor")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      with pytest.raises(RuntimeError):
          _ = pcv.visualize.pseudocolor(gray_img=img)
  
  
  def test_plantcv_visualize_pseudocolor_bad_background():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_pseudocolor_bad_background")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      with pytest.raises(RuntimeError):
          _ = pcv.visualize.pseudocolor(gray_img=img, mask=mask, background="pink")
  
  
  def test_plantcv_visualize_pseudocolor_bad_padding():
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_pseudocolor_bad_background")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1")
      obj_contour = contours_npz['arr_0']
      with pytest.raises(RuntimeError):
          _ = pcv.visualize.pseudocolor(gray_img=img, mask=mask, obj=obj_contour, obj_padding="pink")
  
  
  def test_plantcv_visualize_pseudocolor_bad_mask():
      # Test with debug = None
      pcv.params.debug = None
  
  
  def test_plantcv_visualize_colorize_masks():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_naive_bayes_classifier")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # Test with debug = "print"
      pcv.params.debug = "print"
      mask = pcv.naive_bayes_classifier(rgb_img=img, pdf_file=os.path.join(TEST_DATA, TEST_PDFS))
      _ = pcv.visualize.colorize_masks(masks=[mask['plant'], mask['background']],
                                       colors=[(0, 0, 0), (1, 1, 1)])
      # Test with debug = "plot"
      pcv.params.debug = "plot"
      _ = pcv.visualize.colorize_masks(masks=[mask['plant'], mask['background']],
                                       colors=[(0, 0, 0), (1, 1, 1)])
      # Test with debug = None
      pcv.params.debug = None
      colored_img = pcv.visualize.colorize_masks(masks=[mask['plant'], mask['background']],
                                                 colors=['red', 'blue'])
      # Assert that the output image has the dimensions of the input image
      assert not np.average(colored_img) == 0
  
  
  def test_plantcv_visualize_colorize_masks_bad_input_empty():
      with pytest.raises(RuntimeError):
          _ = pcv.visualize.colorize_masks(masks=[], colors=[])
  
  
  def test_plantcv_visualize_colorize_masks_bad_input_mismatch_number():
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # Test with debug = "print"
      pcv.params.debug = "print"
      mask = pcv.naive_bayes_classifier(rgb_img=img, pdf_file=os.path.join(TEST_DATA, TEST_PDFS))
      with pytest.raises(RuntimeError):
          _ = pcv.visualize.colorize_masks(masks=[mask['plant'], mask['background']], colors=['red', 'green', 'blue'])
  
  
  def test_plantcv_visualize_colorize_masks_bad_color_input():
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      # Test with debug = "print"
      pcv.params.debug = "print"
      mask = pcv.naive_bayes_classifier(rgb_img=img, pdf_file=os.path.join(TEST_DATA, TEST_PDFS))
      with pytest.raises(RuntimeError):
          _ = pcv.visualize.colorize_masks(masks=[mask['plant'], mask['background']], colors=['red', 1.123])
  
  def test_plantcv_visualize_colorize_label_img():
      label_img = np.array([[1,2,3],[4,5,6],[7,8,9]])
      pcv.params.debug = None
      colored_img = pcv.visualize.colorize_label_img(label_img)
      assert (colored_img.shape[0:-1] == label_img.shape) and colored_img.shape[-1] == 3
  
  
  @pytest.mark.parametrize("bins,lb,ub,title", [[200, 0, 255, "Include Title"], [100, None, None, None]])
  def test_plantcv_visualize_histogram(bins, lb, ub, title):
      # Test with debug = None
      pcv.params.debug = None
      # Read test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      fig_hist, hist_df = pcv.visualize.histogram(img=img, mask=mask, bins=bins, lower_bound=lb, upper_bound=ub,
                                                  title=title, hist_data=True)
      assert all([isinstance(fig_hist, ggplot), isinstance(hist_df, pd.core.frame.DataFrame)])
  
  
  def test_plantcv_visualize_histogram_no_mask():
      # Test with debug = None
      pcv.params.debug = None
      # Read test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      fig_hist = pcv.visualize.histogram(img=img, mask=None)
      assert isinstance(fig_hist, ggplot)
  
  
  def test_plantcv_visualize_histogram_rgb_img():
      # Test with debug = None
      pcv.params.debug = None
      # Test RGB input image
      img_rgb = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      fig_hist = pcv.visualize.histogram(img=img_rgb)
      assert isinstance(fig_hist, ggplot)
  
  
  def test_plantcv_visualize_histogram_multispectral_img():
      # Test with debug = None
      pcv.params.debug = None
      # Test multi-spectral image
      img_rgb = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      img_multi = np.concatenate((img_rgb, img_rgb), axis=2)
      fig_hist = pcv.visualize.histogram(img=img_multi)
      assert isinstance(fig_hist, ggplot)
  
  
  def test_plantcv_visualize_histogram_no_img():
      with pytest.raises(RuntimeError):
          _ = pcv.visualize.histogram(img=None)
  
  
  def test_plantcv_visualize_histogram_array():
      # Read test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      with pytest.raises(RuntimeError):
          _ = pcv.visualize.histogram(img=img[0, :])
  
  
  def test_plantcv_visualize_clustered_contours():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_plot_hist")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_VISUALIZE_BACKGROUND), -1)
      roi_objects = np.load(os.path.join(TEST_DATA, TEST_INPUT_VISUALIZE_CONTOUR), encoding="latin1")
      hierarchy = np.load(os.path.join(TEST_DATA, TEST_INPUT_VISUALIZE_HIERARCHY), encoding="latin1")
      cluster_i = np.load(os.path.join(TEST_DATA, TEST_INPUT_VISUALIZE_CLUSTERS), encoding="latin1")
      objs = [roi_objects[arr_n] for arr_n in roi_objects]
      obj_hierarchy = hierarchy['arr_0']
      cluster = [cluster_i[arr_n] for arr_n in cluster_i]
      # Test in plot mode
      pcv.params.debug = "plot"
      # Reset the saved color scale (can be saved between tests)
      pcv.params.saved_color_scale = None
      _ = pcv.visualize.clustered_contours(img=img1, grouped_contour_indices=cluster, roi_objects=objs,
                                                     roi_obj_hierarchy=obj_hierarchy, bounding=False)
      # Test in print mode
      pcv.params.debug = "print"
      # Reset the saved color scale (can be saved between tests)
      pcv.params.saved_color_scale = None
      cluster_img = pcv.visualize.clustered_contours(img=img, grouped_contour_indices=cluster, roi_objects=objs,
                                           roi_obj_hierarchy=obj_hierarchy, nrow=2, ncol=2, bounding=True)
      assert np.sum(cluster_img) > np.sum(img)
  
  
  def test_plantcv_visualize_colorspaces():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_plot_hist")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      pcv.params.debug = "plot"
      vis_img_small = pcv.visualize.colorspaces(rgb_img=img, original_img=False)
      pcv.params.debug = "print"
      vis_img = pcv.visualize.colorspaces(rgb_img=img)
      assert np.shape(vis_img)[1] > (np.shape(img)[1]) and np.shape(vis_img_small)[1] > (np.shape(img)[1])
  
  
  def test_plantcv_visualize_colorspaces_bad_input():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_plot_hist")
      os.mkdir(cache_dir)
      pcv.params.debug_outdir = cache_dir
      # Read in test data
      img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1)
      with pytest.raises(RuntimeError):
          _ = pcv.visualize.colorspaces(rgb_img=img)
  
  
  def test_plantcv_visualize_overlay_two_imgs():
      pcv.params.debug = None
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_visualize_overlay_two_imgs")
      os.mkdir(cache_dir)
      img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      img2 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY))
  
      pcv.params.debug = None
      out_img = pcv.visualize.overlay_two_imgs(img1=img1, img2=img2)
      sample_pt1 = img1[1445, 1154]
      sample_pt2 = img2[1445, 1154]
      sample_pt3 = out_img[1445, 1154]
      pred_rgb = (sample_pt1 * 0.5) + (sample_pt2 * 0.5)
      pred_rgb = pred_rgb.astype(np.uint8)
      assert np.array_equal(sample_pt3, pred_rgb)
  
  
  def test_plantcv_visualize_overlay_two_imgs_grayscale():
      pcv.params.debug = None
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_visualize_overlay_two_imgs_grayscale")
      os.mkdir(cache_dir)
      img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      img2 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1)
      out_img = pcv.visualize.overlay_two_imgs(img1=img1, img2=img2)
      sample_pt1 = np.array([255, 255, 255], dtype=np.uint8)
      sample_pt2 = np.array([255, 255, 255], dtype=np.uint8)
      sample_pt3 = out_img[1445, 1154]
      pred_rgb = (sample_pt1 * 0.5) + (sample_pt2 * 0.5)
      pred_rgb = pred_rgb.astype(np.uint8)
      assert np.array_equal(sample_pt3, pred_rgb)
  
  
  def test_plantcv_visualize_overlay_two_imgs_bad_alpha():
      pcv.params.debug = None
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_visualize_overlay_two_imgs_bad_alpha")
      os.mkdir(cache_dir)
      img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      img2 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY))
      alpha = -1
      with pytest.raises(RuntimeError):
          _ = pcv.visualize.overlay_two_imgs(img1=img1, img2=img2, alpha=alpha)
  
  
  def test_plantcv_visualize_overlay_two_imgs_size_mismatch():
      pcv.params.debug = None
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_visualize_overlay_two_imgs_size_mismatch")
      os.mkdir(cache_dir)
      img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
      img2 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_CROPPED))
      with pytest.raises(RuntimeError):
          _ = pcv.visualize.overlay_two_imgs(img1=img1, img2=img2)
  
  
  @pytest.mark.parametrize("title", ["Include Title", None])
  def test_plantcv_visualize_obj_size_ecdf(title):
      pcv.params.debug = None
      mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK), -1)
      fig_ecdf = plantcv.plantcv.visualize.obj_size_ecdf(mask=mask, title=title)
      assert isinstance(fig_ecdf, ggplot)
  
  
  # ##############################
  # Tests for the utils subpackage
  # ##############################
  
  def test_plantcv_utils_json2csv():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_utils_json2csv")
      os.mkdir(cache_dir)
      plantcv.utils.json2csv(json_file=os.path.join(TEST_DATA, "merged_output.json"),
                             csv_file=os.path.join(cache_dir, "exports"))
      assert all([os.path.exists(os.path.join(cache_dir, "exports-single-value-traits.csv")),
                  os.path.exists(os.path.join(cache_dir, "exports-multi-value-traits.csv"))])
  
  
  def test_plantcv_utils_json2csv_no_json():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_utils_json2csv_no_json")
      os.mkdir(cache_dir)
      with pytest.raises(IOError):
          plantcv.utils.json2csv(json_file=os.path.join(TEST_DATA, "not_a_file.json"),
                                 csv_file=os.path.join(cache_dir, "exports"))
  
  
  def test_plantcv_utils_json2csv_bad_json():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_utils_json2csv_bad_json")
      os.mkdir(cache_dir)
      with pytest.raises(ValueError):
          plantcv.utils.json2csv(json_file=os.path.join(TEST_DATA, "incorrect_json_data.txt"),
                                 csv_file=os.path.join(cache_dir, "exports"))
  
  
  def test_plantcv_utils_sample_images_snapshot():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_utils_sample_images")
      os.mkdir(cache_dir)
      snapshot_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR)
      img_outdir = os.path.join(cache_dir, "snapshot")
      plantcv.utils.sample_images(source_path=snapshot_dir, dest_path=img_outdir, num=3)
      assert os.path.exists(os.path.join(cache_dir, "snapshot"))
  
  
  def test_plantcv_utils_sample_images_flatdir():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_utils_sample_images")
      os.mkdir(cache_dir)
      flat_dir = os.path.join(TEST_DATA)
      img_outdir = os.path.join(cache_dir, "images")
      plantcv.utils.sample_images(source_path=flat_dir, dest_path=img_outdir, num=30)
      random_images = os.listdir(img_outdir)
      assert all([len(random_images) == 30, len(np.unique(random_images)) == 30])
  
  
  def test_plantcv_utils_sample_images_bad_source():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_utils_sample_images")
      os.mkdir(cache_dir)
      fake_dir = os.path.join(TEST_DATA, "snapshot")
      img_outdir = os.path.join(cache_dir, "images")
      with pytest.raises(IOError):
          plantcv.utils.sample_images(source_path=fake_dir, dest_path=img_outdir, num=3)
  
  
  def test_plantcv_utils_sample_images_bad_flat_num():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_utils_sample_images")
      os.mkdir(cache_dir)
      flat_dir = os.path.join(TEST_DATA)
      img_outdir = os.path.join(cache_dir, "images")
      with pytest.raises(RuntimeError):
          plantcv.utils.sample_images(source_path=flat_dir, dest_path=img_outdir, num=300)
  
  
  def test_plantcv_utils_sample_images_bad_phenofront_num():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_utils_sample_images")
      os.mkdir(cache_dir)
      snapshot_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR)
      img_outdir = os.path.join(cache_dir, "images")
      with pytest.raises(RuntimeError):
          plantcv.utils.sample_images(source_path=snapshot_dir, dest_path=img_outdir, num=300)
  
  
  def test_plantcv_utils_tabulate_bayes_classes():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_utils_tabulate_bayes_classes")
      os.mkdir(cache_dir)
      outfile = os.path.join(cache_dir, "rgb_table.txt")
      plantcv.utils.tabulate_bayes_classes(input_file=os.path.join(TEST_DATA, PIXEL_VALUES), output_file=outfile)
      table = pd.read_csv(outfile, sep="\t")
      assert table.shape == (228, 2)
  
  
  def test_plantcv_utils_tabulate_bayes_classes_missing_input():
      # Test cache directory
      cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_utils_tabulate_bayes_classes_missing_input")
      os.mkdir(cache_dir)
      outfile = os.path.join(cache_dir, "rgb_table.txt")
      with pytest.raises(IOError):
          plantcv.utils.tabulate_bayes_classes(input_file=os.path.join(PIXEL_VALUES), output_file=outfile)
  
  
  # ##############################
  # Clean up test files
  # ##############################
  def teardown_function():
      shutil.rmtree(TEST_TMPDIR)
  