#pragma once

#include <morph/ColourMap_Lists.h>    // Colour map tables from matplotlib
#include <morph/colourmaps_crameri.h> // Colour map tables from Fabio Crameri
#include <morph/lenthe_colormap.hpp>  // William Lenthe's implementation of perceptually uniform colour maps
#include <morph/colourmaps_cet.h>     // Colour map tables from CET

#include <string_view>
#include <stdexcept>
#include <cmath>
#include <cstdint>
#include <morph/tools.h>
#include <morph/vec.h>
#include <morph/mathconst.h>
#include <morph/flags.h>
#include <morph/crc32.h>

namespace morph {

    //! Different colour maps types.
    enum class ColourMapType : uint32_t
    {
        // Python matplotlib maps
        Magma,
        Inferno,
        Plasma,
        Viridis,
        Cividis,
        Twilight,

        // Fabio Crameri's maps, which include colourblind-accessible maps.
        // See https://www.fabiocrameri.ch/colourmaps/ or https://zenodo.org/records/8409685
        Devon,
        NaviaW,
        BrocO,
        Acton,
        Batlow,
        Berlin,
        Tofino,
        Broc,
        CorkO,
        Lapaz,
        BamO,
        Vanimo,
        Lajolla,
        Lisbon,
        GrayC,
        Roma,
        Vik,
        Navia,
        Bilbao,
        Turku,
        Lipari,
        VikO,
        BatlowK,
        Oslo,
        Oleron,
        Davos,
        Fes,
        Managua,
        Glasgow,
        Tokyo,
        Bukavu,
        Bamako,
        BatlowW,
        Nuuk,
        Cork,
        Hawaii,
        Bam,
        Imola,
        RomaO,
        Buda,

        // Maps which generate from William Lenthe's UniformBicone code (https://github.com/wlenthe/UniformBicone)
        Fire,       // Very similar to inferno
        Ocean,      // Similar to Viridis
        Ice,
        DivBlueRed,
        CyclicGrey,
        CyclicFour, // Almost identical to CET_C2
        CyclicSix,
        CyclicDivBlueRed,
        DiscFourWhite,
        DiscFourBlack,
        DiscSixWhite,  // This is the perceptually invariant HSV equivalent
        DiscSixBlack,

        // CET lookup tables
        // Section for ColourMapType enum
        CET_L02,
        CET_L13,
        CET_C4,
        CET_D04,
        CET_L12,
        CET_C1s,
        CET_L01,
        CET_C5,
        CET_D11,
        CET_L04,
        CET_CBL2,
        CET_C4s,
        CET_L15,
        CET_L20,
        CET_CBD1,
        CET_D06,
        CET_I3,
        CET_D01A,
        CET_L16,
        CET_L06,
        CET_C2s,
        CET_I1,
        CET_C7s,
        CET_I2,
        CET_C6s,
        CET_C6,
        CET_L05,
        CET_D08,
        CET_L03,
        CET_L14,
        CET_C2,
        CET_R3,
        CET_D01,
        CET_C1,
        CET_D02,
        CET_CBC1,
        CET_D09,
        CET_L10,
        CET_R1,
        CET_C3,
        CET_CBL1,
        CET_C3s,
        CET_C5s,
        CET_L08,
        CET_R4,
        CET_R2,
        CET_L11,
        CET_D10,
        CET_D07,
        CET_L17,
        CET_D12,
        CET_CBC2,
        CET_D13,
        CET_D03,
        CET_C7,
        CET_L07,
        CET_L09,
        CET_L18,
        CET_L19,

        // Other sources
        Petrov,       // The cosmic microwave background radiation colourmap created by Matthew Petrov
                      // as an improvement to the Planck colour map

        // Maps generated by simplistic algorithms (Poor perceptual uniformity)
        Jet,              // The Jet colour map, but not. You'll actually get CET-R4 now.
        Rainbow,          // A simple rainbow... but not; you'll get CET-C2.
        RainbowZeroBlack, // As Rainbow, but if datum is 0, then colour is pure black.
        RainbowZeroWhite, // As Rainbow, but if datum is 0, then colour is pure white.
        Greyscale,        // Greyscale is now generated from William Lenthe's ramp::gray
        GreyscaleInv,     // Inverted Greyscale. High signal gives light greys to white
        Monochrome,       // Monochrome is 'monohue': fixed hue and value; vary the *saturation* from 0 to this->sat.
        MonochromeRed,    // Note that Monochrome maps do not have very good perceptual uniformity
        MonochromeBlue,   // though the blue one isn't too bad
        MonochromeGreen,
        Monoval,          // Monoval varies the *value* of the colour with fixed hue and saturation.
        MonovalRed,       // Monoval maps have better perceptual uniformity than Monochrome maps
        MonovalBlue,
        MonovalGreen,
        HSV1D,            // A 1D version of HSV, traverses a line across the HSV circle for a set value of hue,
                          // which determines what colour the value 1 will return. Useful for positive/negative ranges, but
                          // perceptual uniformity is not great.

        Duochrome,    // Two fixed hues, vary saturation of each with two input numbers.
        Trichrome,    // As for Duochrome, but with three inputs
        RGB,          // A kind of 'null' colour map that takes R, G and B values and returns as an RGB colour.
                      // Of course, you don't really need a morph::ColourMap to do this, but it can be useful where
                      // the ColourMap is embedded in the workflow, such as in a VisualDataModel.
        RGBMono,      // Takes RGB input and outputs a coloured monochrome version (datum varies value)
        RGBGrey,      // Takes RGB input and outputs a greyscale version
        HSV,          // A special map in which two input numbers are used to compute a hue and a saturation.
        Fixed,        // Fixed colour. Should return same colour for any datum. User must set hue, sat, val.

        N_entries
    };
    // Define prefix increment and decrement operators for the ColourMapType enum class.
    morph::ColourMapType& operator++(morph::ColourMapType& t)
    {
        t = static_cast<morph::ColourMapType>((static_cast<uint32_t>(t) + 1u) % static_cast<uint32_t>(morph::ColourMapType::N_entries));
        return t;
    }
    morph::ColourMapType& operator--(morph::ColourMapType& t)
    {
        uint32_t ti = static_cast<uint32_t>(t);
        ti = (ti == 0u ? static_cast<uint32_t>(morph::ColourMapType::N_entries) - 1u : ti - 1u);
        t = static_cast<morph::ColourMapType>(ti);
        return t;
    }

    // A flags class for ColourMaps, to flag features
    enum class ColourMapFlags : uint32_t
    {
        one_d,
        two_d,
        three_d,
        sequential, // sequential/linear
        cyclic,
        disc,
        perceptually_uniform,
        colourblind_friendly,
        divergent,
        rainbow,
        isoluminant,
        multisequential,
        whites,
        reds,
        blues,      // Flag 15
        greens,
        yellows,
        pinks,
        purples,
        greys
    };

    // Return a set of ColourMapFlags suitable for ColourMapType t
    morph::flags<morph::ColourMapFlags> makeColourMapFlags (const morph::ColourMapType t)
    {
        // Logic to create default flags
        morph::flags<morph::ColourMapFlags> f(uint32_t{0});

        // Dimensionality
        if (t == ColourMapType::DiscFourWhite
            || t == ColourMapType::DiscFourBlack
            || t == ColourMapType::DiscSixWhite
            || t == ColourMapType::DiscSixBlack
            || t == ColourMapType::HSV
            || t == ColourMapType::Duochrome) {
            f |= ColourMapFlags::two_d;
        } else if (t == ColourMapType::Trichrome
                   || t == ColourMapType::RGB
                   || t == ColourMapType::RGBMono
                   || t == ColourMapType::RGBGrey) {
            f |= ColourMapFlags::three_d;
        } else {
            f |= ColourMapFlags::one_d;
        }

        // Linear (sequential)
        if (t == ColourMapType::CET_L01
            || t == ColourMapType::CET_L02
            || t == ColourMapType::CET_L03
            || t == ColourMapType::CET_L04
            || t == ColourMapType::CET_L05
            || t == ColourMapType::CET_L06
            || t == ColourMapType::CET_L07
            || t == ColourMapType::CET_L08
            || t == ColourMapType::CET_L09
            || t == ColourMapType::CET_L10
            || t == ColourMapType::CET_L11
            || t == ColourMapType::CET_L12
            || t == ColourMapType::CET_L13
            || t == ColourMapType::CET_L14
            || t == ColourMapType::CET_L15
            || t == ColourMapType::CET_L16
            || t == ColourMapType::CET_L17
            || t == ColourMapType::CET_L18
            || t == ColourMapType::CET_L19
            || t == ColourMapType::CET_L20
            || t == ColourMapType::CET_CBL1
            || t == ColourMapType::CET_CBL2


            || t == ColourMapType::Batlow
            || t == ColourMapType::BatlowW
            || t == ColourMapType::BatlowK
            || t == ColourMapType::Glasgow
            || t == ColourMapType::Lipari
            || t == ColourMapType::Navia
            || t == ColourMapType::Hawaii
            || t == ColourMapType::Buda
            || t == ColourMapType::Imola
            || t == ColourMapType::Oslo
            || t == ColourMapType::GrayC
            || t == ColourMapType::Nuuk
            || t == ColourMapType::Devon
            || t == ColourMapType::Lajolla
            || t == ColourMapType::Bamako
            || t == ColourMapType::Davos
            || t == ColourMapType::Bilbao
            || t == ColourMapType::Lapaz
            || t == ColourMapType::Acton
            || t == ColourMapType::Turku
            || t == ColourMapType::Tokyo

            || t == ColourMapType::Inferno
            || t == ColourMapType::Plasma
            || t == ColourMapType::Cividis
            || t == ColourMapType::Viridis
            || t == ColourMapType::Magma

            || t == ColourMapType::Greyscale
            || t == ColourMapType::GreyscaleInv
            || t == ColourMapType::Monochrome
            || t == ColourMapType::MonochromeRed
            || t == ColourMapType::MonochromeGreen
            || t == ColourMapType::MonochromeBlue
            || t == ColourMapType::Monoval
            || t == ColourMapType::MonovalRed
            || t == ColourMapType::MonovalGreen
            || t == ColourMapType::MonovalBlue ) {

            f |= ColourMapFlags::sequential;
        }

        // Divergent
        if (t == ColourMapType::Petrov
            || t == ColourMapType::HSV1D

            || t == ColourMapType::CET_D01
            || t == ColourMapType::CET_D01A
            || t == ColourMapType::CET_D02
            || t == ColourMapType::CET_D03
            || t == ColourMapType::CET_D04
            || t == ColourMapType::CET_D07
            || t == ColourMapType::CET_D08
            || t == ColourMapType::CET_D09
            || t == ColourMapType::CET_D10
            || t == ColourMapType::CET_D13
            || t == ColourMapType::CET_R3
            || t == ColourMapType::CET_CBD1


            || t == ColourMapType::Broc
            || t == ColourMapType::Cork
            || t == ColourMapType::Vik
            || t == ColourMapType::Lisbon
            || t == ColourMapType::Tofino
            || t == ColourMapType::Berlin
            || t == ColourMapType::Bam
            || t == ColourMapType::Roma
            || t == ColourMapType::Vanimo
            || t == ColourMapType::Managua ) {
            f |= ColourMapFlags::divergent;
        }

        // Cyclic
        if (t == ColourMapType::CET_C1
            || t == ColourMapType::CET_C2
            || t == ColourMapType::CET_C3
            || t == ColourMapType::CET_C4
            || t == ColourMapType::CET_C5
            || t == ColourMapType::CET_C6
            || t == ColourMapType::CET_C7
            || t == ColourMapType::CET_CBC1
            || t == ColourMapType::CET_CBC2

            || t == ColourMapType::Twilight

            || t == ColourMapType::RomaO
            || t == ColourMapType::BamO
            || t == ColourMapType::BrocO
            || t == ColourMapType::CorkO
            || t == ColourMapType::VikO
            ) {
            f |= ColourMapFlags::cyclic;
        }

        // Multi-sequential
        if (t == ColourMapType::Oleron
            || t == ColourMapType::Bukavu
            || t == ColourMapType::Fes ) {
            f |= ColourMapFlags::multisequential;
        }

        // Isoluminant
        if (t == ColourMapType::CET_I1
            || t == ColourMapType::CET_I2
            || t == ColourMapType::CET_I3
            || t == ColourMapType::CET_D11
            || t == ColourMapType::CET_D12 ) {
            f |= ColourMapFlags::isoluminant;
        }

        // Rainbow maps
        if (t == ColourMapType::Jet
            || t == ColourMapType::Rainbow
            || t == ColourMapType::Hawaii
            || t == ColourMapType::CET_R1
            || t == ColourMapType::CET_R2
            || t == ColourMapType::CET_R3
            || t == ColourMapType::CET_R4
            || t == ColourMapType::CET_C1
            || t == ColourMapType::CET_C2
            || t == ColourMapType::CET_C6
            || t == ColourMapType::CET_C7
            || t == ColourMapType::HSV
            || t == ColourMapType::DiscFourWhite
            || t == ColourMapType::DiscFourBlack
            || t == ColourMapType::DiscSixWhite
            || t == ColourMapType::DiscSixBlack) {
            f |= ColourMapFlags::rainbow;
        }

        // Disc maps
        if (t == ColourMapType::DiscFourWhite
            || t == ColourMapType::DiscFourBlack
            || t == ColourMapType::DiscSixWhite
            || t == ColourMapType::DiscSixBlack) {
            f |= ColourMapFlags::disc;
        }

        // PU
        if (t == ColourMapType::HSV
            || t == ColourMapType::HSV1D
            || t == ColourMapType::Monochrome
            || t == ColourMapType::MonochromeRed
            || t == ColourMapType::MonochromeGreen
            || t == ColourMapType::MonochromeBlue
            || t == ColourMapType::Monoval
            || t == ColourMapType::MonovalRed
            || t == ColourMapType::MonovalGreen
            || t == ColourMapType::MonovalBlue
            || t == ColourMapType::Rainbow) {
            // Not perceptually uniform
        } else {
            f |= ColourMapFlags::perceptually_uniform;
        }

        // Colourblind friendly
        if (t == ColourMapType::Monochrome
            || t == ColourMapType::MonochromeRed
            || t == ColourMapType::MonochromeGreen
            || t == ColourMapType::MonochromeBlue
            || t == ColourMapType::Monoval
            || t == ColourMapType::MonovalRed
            || t == ColourMapType::MonovalGreen
            || t == ColourMapType::MonovalBlue
            || t == ColourMapType::Greyscale
            || t == ColourMapType::GreyscaleInv

            //|| t == ColourMapType::CET_CL01
            //|| t == ColourMapType::CET_CL02
            //|| t == ColourMapType::CET_CL06
            //|| t == ColourMapType::CET_CL12
            //|| t == ColourMapType::CET_CL13
            //|| t == ColourMapType::CET_CL14
            //|| t == ColourMapType::CET_CL15
            || t == ColourMapType::CET_C5

            || t == ColourMapType::CET_CBL1
            || t == ColourMapType::CET_CBL2
            || t == ColourMapType::CET_CBD1
            || t == ColourMapType::CET_CBC1
            || t == ColourMapType::CET_CBC2

            || t == ColourMapType::GrayC

            || t == ColourMapType::Batlow
            || t == ColourMapType::BatlowW
            || t == ColourMapType::BatlowK
            || t == ColourMapType::Glasgow
            || t == ColourMapType::Lipari
            || t == ColourMapType::Navia
            || t == ColourMapType::Hawaii
            || t == ColourMapType::Buda
            || t == ColourMapType::Imola
            || t == ColourMapType::Oslo
            || t == ColourMapType::GrayC
            || t == ColourMapType::Nuuk
            || t == ColourMapType::Devon
            || t == ColourMapType::Lajolla
            || t == ColourMapType::Bamako
            || t == ColourMapType::Davos
            || t == ColourMapType::Bilbao
            || t == ColourMapType::Lapaz
            || t == ColourMapType::Acton
            || t == ColourMapType::Turku
            || t == ColourMapType::Tokyo

            || t == ColourMapType::RomaO
            || t == ColourMapType::BamO
            || t == ColourMapType::BrocO
            || t == ColourMapType::CorkO
            || t == ColourMapType::VikO

            || t == ColourMapType::Broc
            || t == ColourMapType::Cork
            || t == ColourMapType::Vik
            || t == ColourMapType::Lisbon
            || t == ColourMapType::Tofino
            || t == ColourMapType::Berlin
            || t == ColourMapType::Bam
            || t == ColourMapType::Roma
            || t == ColourMapType::Vanimo
            || t == ColourMapType::Managua

            || t == ColourMapType::Oleron
            || t == ColourMapType::Bukavu
            || t == ColourMapType::Fes ) {
            f |= ColourMapFlags::colourblind_friendly;
        }
        return f;
    }

    /*!
     * Colour mapping
     *
     * \tparam T The type of the datum used to traverse the colour map. When this is a
     * floating point type, then the input datum should be in the range 0.0 to 1.0. If
     * an integral type, then what? For char/unsigned char then 0-127 or 0-255. When
     * unsigned short then 0-MAX also. What about if unsigned int or larger? Surely not
     * the full range of these? Allow a runtime choice?
     *
     * A simple use of a ColourMap might look like this:
     *
     * // Instantiate a colourmap which will convert floats (range [0,1]) into colours from the 'Plasma' colour map.
     * morph::ColourMap<float> cm1(morph::ColourMapType::Plasma);
     *
     * // Convert floats into colours using the ColourMap::convert function:
     * std::array<float, 3> clr1 = cm1.convert (0.2f);
     * std::array<float, 3> clr2 = cm1.convert (0.8f);
     *
     * which gives two colours (clr1 and clr2) with one encoding a low value (clr1) and
     * one encoding a high value (clr2).
     */
    template <typename T>
    class ColourMap
    {
    public:
        //! Feature flags associated with the ColourMapType (generated in setType)
        morph::flags<ColourMapFlags> flags;
    private:
        //! Type of map
        ColourMapType type = ColourMapType::Plasma;
        //! The hue (range 0 to 1.0f) as used in HSV colour values for Monochrome maps.
        float hue = 0.0f;
        //! The saturation
        float sat = 1.0f;
        //! The value
        float val = 1.0f;

        // Used by Duochrome
        float hue2 = 0.33f;
        float sat2 = 1.0f;
        float val2 = 1.0f;

        // Used by Trichrome
        float hue3 = 0.66f;
        float sat3 = 1.0f;
        float val3 = 1.0f;

        // Used by HSV and other disc maps. This allows you to rotate the way the HSV hues
        // appear. If your two inputs were 'x' and 'y', then this allows you to choose which colour
        // appears for x=1 and y=0 (i.e. for 0 rads). Give this as a value in radians in range 0 to
        // 2pi. 0 gives the default, red.
        T hue_rotation = T{0};

        // Set this true to reverse the direction in which the hues are output for disc maps
        bool hue_reverse_direction = false;

        //! If true, as ColourMapType type is a 1D map, then expect to operate in '2D mode' with
        //! convert (T, T). The second dimension of the input is used to set the saturation of the
        //! colour retrieved from the map.
        bool act_2d = false;

    public:
        //! Default constructor is required, but need not do anything.
        ColourMap() {}
        //! Construct with a type
        ColourMap (ColourMapType _t) { this->setType (_t); }
        //! Construct with the string name of the type
        ColourMap (const std::string& _t) { this->setType (_t); }

        //! If s is a string that matches a ColourMapType, return that colour map
        //! type. If string doesn't match, return the default.
        static ColourMapType strToColourMapType (const std::string& s)
        {
            // Allow use of string_view constants: "like this"sv
#ifndef _MSC_VER
            using std::literals::string_view_literals::operator""sv;
#else
            using namespace std::literals;
#endif
            ColourMapType cmt = morph::ColourMapType::Plasma;
            std::string _s = s;
            morph::tools::toLowerCase (_s);
            uint32_t _s_crc = morph::crc32 (_s);
            switch (_s_crc) {
            case morph::crc32 ("fixed"sv):
                cmt = morph::ColourMapType::Fixed; break;
            case morph::crc32 ("trichrome"sv):
                cmt = morph::ColourMapType::Trichrome; break;
            case morph::crc32 ("duochrome"sv):
                cmt = morph::ColourMapType::Duochrome; break;
            case morph::crc32 ("rgb"sv):
                cmt = morph::ColourMapType::RGB; break;
            case morph::crc32 ("rgbmono"sv):
                cmt = morph::ColourMapType::RGBMono; break;
            case morph::crc32 ("rgbgrey"sv):
                cmt = morph::ColourMapType::RGBGrey; break;
            case morph::crc32 ("hsv"sv):
                cmt = morph::ColourMapType::HSV; break;
            case morph::crc32 ("hsv1d"sv):
                cmt = morph::ColourMapType::HSV1D; break;
            case morph::crc32 ("monochromegreen"sv):
                cmt = morph::ColourMapType::MonochromeGreen; break;
            case morph::crc32 ("monochromeblue"sv):
                cmt = morph::ColourMapType::MonochromeBlue; break;
            case morph::crc32 ("monochromered"sv):
                cmt = morph::ColourMapType::MonochromeRed; break;
            case morph::crc32 ("monochrome"sv):
                cmt = morph::ColourMapType::Monochrome; break;
            case morph::crc32 ("monovalgreen"sv):
                cmt = morph::ColourMapType::MonovalGreen; break;
            case morph::crc32 ("monovalblue"sv):
                cmt = morph::ColourMapType::MonovalBlue; break;
            case morph::crc32 ("monovalred"sv):
                cmt = morph::ColourMapType::MonovalRed; break;
            case morph::crc32 ("monoval"sv):
                cmt = morph::ColourMapType::Monoval; break;
            case morph::crc32 ("greyscale"sv):
                cmt = morph::ColourMapType::Greyscale; break;
            case morph::crc32 ("greyscaleinv"sv):
                cmt = morph::ColourMapType::GreyscaleInv; break;
            case morph::crc32 ("twilight"sv):
                cmt = morph::ColourMapType::Twilight; break;
            case morph::crc32 ("petrov"sv):
                cmt = morph::ColourMapType::Petrov; break;

            // Crameri
            case morph::crc32 ("devon"sv):
                cmt = morph::ColourMapType::Devon; break;
            case morph::crc32 ("naviaw"sv):
                cmt = morph::ColourMapType::NaviaW; break;
            case morph::crc32 ("broco"sv):
                cmt = morph::ColourMapType::BrocO; break;
            case morph::crc32 ("acton"sv):
                cmt = morph::ColourMapType::Acton; break;
            case morph::crc32 ("batlow"sv):
                cmt = morph::ColourMapType::Batlow; break;
            case morph::crc32 ("berlin"sv):
                cmt = morph::ColourMapType::Berlin; break;
            case morph::crc32 ("tofino"sv):
                cmt = morph::ColourMapType::Tofino; break;
            case morph::crc32 ("broc"sv):
                cmt = morph::ColourMapType::Broc; break;
            case morph::crc32 ("corko"sv):
                cmt = morph::ColourMapType::CorkO; break;
            case morph::crc32 ("lapaz"sv):
                cmt = morph::ColourMapType::Lapaz; break;
            case morph::crc32 ("bamo"sv):
                cmt = morph::ColourMapType::BamO; break;
            case morph::crc32 ("vanimo"sv):
                cmt = morph::ColourMapType::Vanimo; break;
            case morph::crc32 ("lajolla"sv):
                cmt = morph::ColourMapType::Lajolla; break;
            case morph::crc32 ("lisbon"sv):
                cmt = morph::ColourMapType::Lisbon; break;
            case morph::crc32 ("grayc"sv):
                cmt = morph::ColourMapType::GrayC; break;
            case morph::crc32 ("roma"sv):
                cmt = morph::ColourMapType::Roma; break;
            case morph::crc32 ("vik"sv):
                cmt = morph::ColourMapType::Vik; break;
            case morph::crc32 ("navia"sv):
                cmt = morph::ColourMapType::Navia; break;
            case morph::crc32 ("bilbao"sv):
                cmt = morph::ColourMapType::Bilbao; break;
            case morph::crc32 ("turku"sv):
                cmt = morph::ColourMapType::Turku; break;
            case morph::crc32 ("lipari"sv):
                cmt = morph::ColourMapType::Lipari; break;
            case morph::crc32 ("viko"sv):
                cmt = morph::ColourMapType::VikO; break;
            case morph::crc32 ("batlowk"sv):
                cmt = morph::ColourMapType::BatlowK; break;
            case morph::crc32 ("oslo"sv):
                cmt = morph::ColourMapType::Oslo; break;
            case morph::crc32 ("oleron"sv):
                cmt = morph::ColourMapType::Oleron; break;
            case morph::crc32 ("davos"sv):
                cmt = morph::ColourMapType::Davos; break;
            case morph::crc32 ("fes"sv):
                cmt = morph::ColourMapType::Fes; break;
            case morph::crc32 ("managua"sv):
                cmt = morph::ColourMapType::Managua; break;
            case morph::crc32 ("glasgow"sv):
                cmt = morph::ColourMapType::Glasgow; break;
            case morph::crc32 ("tokyo"sv):
                cmt = morph::ColourMapType::Tokyo; break;
            case morph::crc32 ("bukavu"sv):
                cmt = morph::ColourMapType::Bukavu; break;
            case morph::crc32 ("bamako"sv):
                cmt = morph::ColourMapType::Bamako; break;
            case morph::crc32 ("batloww"sv):
                cmt = morph::ColourMapType::BatlowW; break;
            case morph::crc32 ("nuuk"sv):
                cmt = morph::ColourMapType::Nuuk; break;
            case morph::crc32 ("cork"sv):
                cmt = morph::ColourMapType::Cork; break;
            case morph::crc32 ("hawaii"sv):
                cmt = morph::ColourMapType::Hawaii; break;
            case morph::crc32 ("bam"sv):
                cmt = morph::ColourMapType::Bam; break;
            case morph::crc32 ("imola"sv):
                cmt = morph::ColourMapType::Imola; break;
            case morph::crc32 ("romao"sv):
                cmt = morph::ColourMapType::RomaO; break;
            case morph::crc32 ("buda"sv):
                cmt = morph::ColourMapType::Buda; break;

            // Lenthe
            case morph::crc32 ("fire"sv):
                cmt = morph::ColourMapType::Fire; break;
            case morph::crc32 ("ocean"sv):
                cmt = morph::ColourMapType::Ocean; break;
            case morph::crc32 ("ice"sv):
                cmt = morph::ColourMapType::Ice; break;
            case morph::crc32 ("divbluered"sv):
                cmt = morph::ColourMapType::DivBlueRed; break;
            case morph::crc32 ("cyclicgrey"sv):
                cmt = morph::ColourMapType::CyclicGrey; break;
            case morph::crc32 ("cyclicfour"sv):
                cmt = morph::ColourMapType::CyclicFour; break;
            case morph::crc32 ("cyclicsix"sv):
                cmt = morph::ColourMapType::CyclicSix; break;
            case morph::crc32 ("cyclicdivbluered"sv):
                cmt = morph::ColourMapType::CyclicDivBlueRed; break;
            case morph::crc32 ("discfourwhite"sv):
                cmt = morph::ColourMapType::DiscFourWhite; break;
            case morph::crc32 ("discfourblack"sv):
                cmt = morph::ColourMapType::DiscFourBlack; break;
            case morph::crc32 ("discsixwhite"sv):
                cmt = morph::ColourMapType::DiscSixWhite; break;
            case morph::crc32 ("discsixblack"sv):
                cmt = morph::ColourMapType::DiscSixBlack; break;

            // CET
            case morph::crc32 ("cet_l02"sv):
                cmt = morph::ColourMapType::CET_L02; break;
            case morph::crc32 ("cet_l13"sv):
                cmt = morph::ColourMapType::CET_L13; break;
            case morph::crc32 ("cet_c4"sv):
                cmt = morph::ColourMapType::CET_C4; break;
            case morph::crc32 ("cet_d04"sv):
                cmt = morph::ColourMapType::CET_D04; break;
            case morph::crc32 ("cet_l12"sv):
                cmt = morph::ColourMapType::CET_L12; break;
            case morph::crc32 ("cet_c1s"sv):
                cmt = morph::ColourMapType::CET_C1s; break;
            case morph::crc32 ("cet_l01"sv):
                cmt = morph::ColourMapType::CET_L01; break;
            case morph::crc32 ("cet_c5"sv):
                cmt = morph::ColourMapType::CET_C5; break;
            case morph::crc32 ("cet_d11"sv):
                cmt = morph::ColourMapType::CET_D11; break;
            case morph::crc32 ("cet_l04"sv):
                cmt = morph::ColourMapType::CET_L04; break;
            case morph::crc32 ("cet_cbl2"sv):
                cmt = morph::ColourMapType::CET_CBL2; break;
            case morph::crc32 ("cet_c4s"sv):
                cmt = morph::ColourMapType::CET_C4s; break;
            case morph::crc32 ("cet_l15"sv):
                cmt = morph::ColourMapType::CET_L15; break;
            case morph::crc32 ("cet_l20"sv):
                cmt = morph::ColourMapType::CET_L20; break;
            case morph::crc32 ("cet_cbd1"sv):
                cmt = morph::ColourMapType::CET_CBD1; break;
            case morph::crc32 ("cet_d06"sv):
                cmt = morph::ColourMapType::CET_D06; break;
            case morph::crc32 ("cet_i3"sv):
                cmt = morph::ColourMapType::CET_I3; break;
            case morph::crc32 ("cet_d01a"sv):
                cmt = morph::ColourMapType::CET_D01A; break;
            case morph::crc32 ("cet_l16"sv):
                cmt = morph::ColourMapType::CET_L16; break;
            case morph::crc32 ("cet_l06"sv):
                cmt = morph::ColourMapType::CET_L06; break;
            case morph::crc32 ("cet_c2s"sv):
                cmt = morph::ColourMapType::CET_C2s; break;
            case morph::crc32 ("cet_i1"sv):
                cmt = morph::ColourMapType::CET_I1; break;
            case morph::crc32 ("cet_c7s"sv):
                cmt = morph::ColourMapType::CET_C7s; break;
            case morph::crc32 ("cet_i2"sv):
                cmt = morph::ColourMapType::CET_I2; break;
            case morph::crc32 ("cet_c6s"sv):
                cmt = morph::ColourMapType::CET_C6s; break;
            case morph::crc32 ("cet_c6"sv):
                cmt = morph::ColourMapType::CET_C6; break;
            case morph::crc32 ("cet_l05"sv):
                cmt = morph::ColourMapType::CET_L05; break;
            case morph::crc32 ("cet_d08"sv):
                cmt = morph::ColourMapType::CET_D08; break;
            case morph::crc32 ("cet_l03"sv):
                cmt = morph::ColourMapType::CET_L03; break;
            case morph::crc32 ("cet_l14"sv):
                cmt = morph::ColourMapType::CET_L14; break;
            case morph::crc32 ("cet_c2"sv):
                cmt = morph::ColourMapType::CET_C2; break;
            case morph::crc32 ("cet_r3"sv):
                cmt = morph::ColourMapType::CET_R3; break;
            case morph::crc32 ("cet_d01"sv):
                cmt = morph::ColourMapType::CET_D01; break;
            case morph::crc32 ("cet_c1"sv):
                cmt = morph::ColourMapType::CET_C1; break;
            case morph::crc32 ("cet_d02"sv):
                cmt = morph::ColourMapType::CET_D02; break;
            case morph::crc32 ("cet_cbc1"sv):
                cmt = morph::ColourMapType::CET_CBC1; break;
            case morph::crc32 ("cet_d09"sv):
                cmt = morph::ColourMapType::CET_D09; break;
            case morph::crc32 ("cet_l10"sv):
                cmt = morph::ColourMapType::CET_L10; break;
            case morph::crc32 ("cet_r1"sv):
                cmt = morph::ColourMapType::CET_R1; break;
            case morph::crc32 ("cet_c3"sv):
                cmt = morph::ColourMapType::CET_C3; break;
            case morph::crc32 ("cet_cbl1"sv):
                cmt = morph::ColourMapType::CET_CBL1; break;
            case morph::crc32 ("cet_c3s"sv):
                cmt = morph::ColourMapType::CET_C3s; break;
            case morph::crc32 ("cet_c5s"sv):
                cmt = morph::ColourMapType::CET_C5s; break;
            case morph::crc32 ("cet_l08"sv):
                cmt = morph::ColourMapType::CET_L08; break;
            case morph::crc32 ("cet_r4"sv):
                cmt = morph::ColourMapType::CET_R4; break;
            case morph::crc32 ("cet_r2"sv):
                cmt = morph::ColourMapType::CET_R2; break;
            case morph::crc32 ("cet_l11"sv):
                cmt = morph::ColourMapType::CET_L11; break;
            case morph::crc32 ("cet_d10"sv):
                cmt = morph::ColourMapType::CET_D10; break;
            case morph::crc32 ("cet_d07"sv):
                cmt = morph::ColourMapType::CET_D07; break;
            case morph::crc32 ("cet_l17"sv):
                cmt = morph::ColourMapType::CET_L17; break;
            case morph::crc32 ("cet_d12"sv):
                cmt = morph::ColourMapType::CET_D12; break;
            case morph::crc32 ("cet_cbc2"sv):
                cmt = morph::ColourMapType::CET_CBC2; break;
            case morph::crc32 ("cet_d13"sv):
                cmt = morph::ColourMapType::CET_D13; break;
            case morph::crc32 ("cet_d03"sv):
                cmt = morph::ColourMapType::CET_D03; break;
            case morph::crc32 ("cet_c7"sv):
                cmt = morph::ColourMapType::CET_C7; break;
            case morph::crc32 ("cet_l07"sv):
                cmt = morph::ColourMapType::CET_L07; break;
            case morph::crc32 ("cet_l09"sv):
                cmt = morph::ColourMapType::CET_L09; break;
            case morph::crc32 ("cet_l18"sv):
                cmt = morph::ColourMapType::CET_L18; break;
            case morph::crc32 ("cet_l19"sv):
                cmt = morph::ColourMapType::CET_L19; break;

            case morph::crc32 ("cividis"sv):
                cmt = morph::ColourMapType::Cividis; break;
            case morph::crc32 ("viridis"sv):
                cmt = morph::ColourMapType::Viridis; break;
            case morph::crc32 ("plasma"sv):
                cmt = morph::ColourMapType::Plasma; break;
            case morph::crc32 ("inferno"sv):
                cmt = morph::ColourMapType::Inferno; break;
            case morph::crc32 ("magma"sv):
                cmt = morph::ColourMapType::Magma; break;
            case morph::crc32 ("rainbowzerowhite"sv):
                cmt = morph::ColourMapType::RainbowZeroWhite; break;
            case morph::crc32 ("rainbowzeroblack"sv):
                cmt = morph::ColourMapType::RainbowZeroBlack; break;
            case morph::crc32 ("rainbow"sv):
                cmt = morph::ColourMapType::Rainbow; break;
            case morph::crc32 ("jet"sv):
                cmt = morph::ColourMapType::Jet; break;
            default:
                break; // cmt remains Plasma
            }
            return cmt;
        }

        static std::string colourMapFlagsToStr (const morph::flags<morph::ColourMapFlags>& _f)
        {
            std::string s("");
            if (_f.test (ColourMapFlags::one_d)) { s += (s.empty() ? "1D" : ", 1D"); }
            if (_f.test (ColourMapFlags::two_d)) { s += (s.empty() ? "2D" : ", 2D"); }
            if (_f.test (ColourMapFlags::three_d)) { s += (s.empty() ? "3D" : ", 3D"); }

            if (_f.test (ColourMapFlags::sequential)) { s += (s.empty() ? "Seq" : ", Seq"); }
            if (_f.test (ColourMapFlags::multisequential)) { s += (s.empty() ? "MultiSeq" : ", MultiSeq"); }
            if (_f.test (ColourMapFlags::divergent)) { s += (s.empty() ? "Dvg" : ", Dvg"); }
            if (_f.test (ColourMapFlags::cyclic)) { s += (s.empty() ? "Cyclic" : ", Cyclic"); }
            if (_f.test (ColourMapFlags::disc)) { s += (s.empty() ? "Disc" : ", Disc"); }

            if (_f.test (ColourMapFlags::isoluminant)) { s += (s.empty() ? "Iso" : ", Iso"); }
            if (_f.test (ColourMapFlags::rainbow)) { s += (s.empty() ? "Rain" : ", Rain"); }

            if (_f.test (ColourMapFlags::perceptually_uniform)) { s += (s.empty() ? "PU" : ", PU"); }
            if (_f.test (ColourMapFlags::colourblind_friendly)) { s += (s.empty() ? "CB" : ", CB"); }

            return s;
        }

        //! Return a string representation of the ColourMapType _t
        static std::string colourMapTypeToStr (const ColourMapType _t)
        {
            std::string s("unknown");
            switch (_t) {
            case morph::ColourMapType::Fixed:
            {
                s = "Fixed";
                break;
            }
            case morph::ColourMapType::Trichrome:
            {
                s = "Trichrome";
                break;
            }
            case morph::ColourMapType::Duochrome:
            {
                s = "Duochrome";
                break;
            }
            case morph::ColourMapType::RGB:
            {
                s = "RGB";
                break;
            }
            case morph::ColourMapType::RGBMono:
            {
                s = "RGBMono";
                break;
            }
            case morph::ColourMapType::RGBGrey:
            {
                s = "RGBGrey";
                break;
            }
            case morph::ColourMapType::HSV:
            {
                s = "HSV";
                break;
            }
            case morph::ColourMapType::HSV1D:
            {
                s = "HSV1D";
                break;
            }
            case morph::ColourMapType::MonochromeGreen:
            {
                s = "MonochromeGreen";
                break;
            }
            case morph::ColourMapType::MonochromeBlue:
            {
                s = "MonochromeBlue";
                break;
            }
            case morph::ColourMapType::MonochromeRed:
            {
                s = "MonochromeRed";
                break;
            }
            case morph::ColourMapType::Monochrome:
            {
                s = "Monochrome";
                break;
            }
            case morph::ColourMapType::MonovalGreen:
            {
                s = "MonovalGreen";
                break;
            }
            case morph::ColourMapType::MonovalBlue:
            {
                s = "MonovalBlue";
                break;
            }
            case morph::ColourMapType::MonovalRed:
            {
                s = "MonovalRed";
                break;
            }
            case morph::ColourMapType::Monoval:
            {
                s = "Monoval";
                break;
            }
            case morph::ColourMapType::Greyscale:
            {
                s = "Greyscale";
                break;
            }
            case morph::ColourMapType::GreyscaleInv:
            {
                s = "GreyscaleInv";
                break;
            }
            case morph::ColourMapType::Twilight:
            {
                s = "Twilight";
                break;
            }
            case morph::ColourMapType::Petrov:
            {
                s = "Petrov";
                break;
            }

            case morph::ColourMapType::Devon:
            {
                s = "Devon";
                break;
            }
            case morph::ColourMapType::NaviaW:
            {
                s = "NaviaW";
                break;
            }
            case morph::ColourMapType::BrocO:
            {
                s = "BrocO";
                break;
            }
            case morph::ColourMapType::Acton:
            {
                s = "Acton";
                break;
            }
            case morph::ColourMapType::Batlow:
            {
                s = "Batlow";
                break;
            }
            case morph::ColourMapType::Berlin:
            {
                s = "Berlin";
                break;
            }
            case morph::ColourMapType::Tofino:
            {
                s = "Tofino";
                break;
            }
            case morph::ColourMapType::Broc:
            {
                s = "Broc";
                break;
            }
            case morph::ColourMapType::CorkO:
            {
                s = "CorkO";
                break;
            }
            case morph::ColourMapType::Lapaz:
            {
                s = "Lapaz";
                break;
            }
            case morph::ColourMapType::BamO:
            {
                s = "BamO";
                break;
            }
            case morph::ColourMapType::Vanimo:
            {
                s = "Vanimo";
                break;
            }
            case morph::ColourMapType::Lajolla:
            {
                s = "Lajolla";
                break;
            }
            case morph::ColourMapType::Lisbon:
            {
                s = "Lisbon";
                break;
            }
            case morph::ColourMapType::GrayC:
            {
                s = "GrayC";
                break;
            }
            case morph::ColourMapType::Roma:
            {
                s = "Roma";
                break;
            }
            case morph::ColourMapType::Vik:
            {
                s = "Vik";
                break;
            }
            case morph::ColourMapType::Navia:
            {
                s = "Navia";
                break;
            }
            case morph::ColourMapType::Bilbao:
            {
                s = "Bilbao";
                break;
            }
            case morph::ColourMapType::Turku:
            {
                s = "Turku";
                break;
            }
            case morph::ColourMapType::Lipari:
            {
                s = "Lipari";
                break;
            }
            case morph::ColourMapType::VikO:
            {
                s = "VikO";
                break;
            }
            case morph::ColourMapType::BatlowK:
            {
                s = "BatlowK";
                break;
            }
            case morph::ColourMapType::Oslo:
            {
                s = "Oslo";
                break;
            }
            case morph::ColourMapType::Oleron:
            {
                s = "Oleron";
                break;
            }
            case morph::ColourMapType::Davos:
            {
                s = "Davos";
                break;
            }
            case morph::ColourMapType::Fes:
            {
                s = "Fes";
                break;
            }
            case morph::ColourMapType::Managua:
            {
                s = "Managua";
                break;
            }
            case morph::ColourMapType::Glasgow:
            {
                s = "Glasgow";
                break;
            }
            case morph::ColourMapType::Tokyo:
            {
                s = "Tokyo";
                break;
            }
            case morph::ColourMapType::Bukavu:
            {
                s = "Bukavu";
                break;
            }
            case morph::ColourMapType::Bamako:
            {
                s = "Bamako";
                break;
            }
            case morph::ColourMapType::BatlowW:
            {
                s = "BatlowW";
                break;
            }
            case morph::ColourMapType::Nuuk:
            {
                s = "Nuuk";
                break;
            }
            case morph::ColourMapType::Cork:
            {
                s = "Cork";
                break;
            }
            case morph::ColourMapType::Hawaii:
            {
                s = "Hawaii";
                break;
            }
            case morph::ColourMapType::Bam:
            {
                s = "Bam";
                break;
            }
            case morph::ColourMapType::Imola:
            {
                s = "Imola";
                break;
            }
            case morph::ColourMapType::RomaO:
            {
                s = "RomaO";
                break;
            }
            case morph::ColourMapType::Buda:
            {
                s = "Buda";
                break;
            }

            case morph::ColourMapType::Fire:
            {
                s = "Fire";
                break;
            }
            case morph::ColourMapType::Ocean:
            {
                s = "Ocean";
                break;
            }
            case morph::ColourMapType::Ice:
            {
                s = "Ice";
                break;
            }
            case morph::ColourMapType::DivBlueRed:
            {
                s = "DivBlueRed";
                break;
            }
            case morph::ColourMapType::CyclicGrey:
            {
                s = "CyclicGrey";
                break;
            }
            case morph::ColourMapType::CyclicFour:
            {
                s = "CyclicFour";
                break;
            }
            case morph::ColourMapType::CyclicSix:
            {
                s = "CyclicSix";
                break;
            }
            case morph::ColourMapType::CyclicDivBlueRed:
            {
                s = "CyclicDivBlueRed";
                break;
            }
            case morph::ColourMapType::DiscFourWhite:
            {
                s = "DiscFourWhite";
                break;
            }
            case morph::ColourMapType::DiscFourBlack:
            {
                s = "DiscFourBlack";
                break;
            }
            case morph::ColourMapType::DiscSixWhite:
            {
                s = "DiscSixWhite";
                break;
            }
            case morph::ColourMapType::DiscSixBlack:
            {
                s = "DiscSixBlack";
                break;
            }

            case morph::ColourMapType::CET_L02:
            {
                s = "CET_L02";
                break;
            }
            case morph::ColourMapType::CET_L13:
            {
                s = "CET_L13";
                break;
            }
            case morph::ColourMapType::CET_C4:
            {
                s = "CET_C4";
                break;
            }
            case morph::ColourMapType::CET_D04:
            {
                s = "CET_D04";
                break;
            }
            case morph::ColourMapType::CET_L12:
            {
                s = "CET_L12";
                break;
            }
            case morph::ColourMapType::CET_C1s:
            {
                s = "CET_C1S";
                break;
            }
            case morph::ColourMapType::CET_L01:
            {
                s = "CET_L01";
                break;
            }
            case morph::ColourMapType::CET_C5:
            {
                s = "CET_C5";
                break;
            }
            case morph::ColourMapType::CET_D11:
            {
                s = "CET_D11";
                break;
            }
            case morph::ColourMapType::CET_L04:
            {
                s = "CET_L04";
                break;
            }
            case morph::ColourMapType::CET_CBL2:
            {
                s = "CET_CBL2";
                break;
            }
            case morph::ColourMapType::CET_C4s:
            {
                s = "CET_C4S";
                break;
            }
            case morph::ColourMapType::CET_L15:
            {
                s = "CET_L15";
                break;
            }
            case morph::ColourMapType::CET_L20:
            {
                s = "CET_L20";
                break;
            }
            case morph::ColourMapType::CET_CBD1:
            {
                s = "CET_CBD1";
                break;
            }
            case morph::ColourMapType::CET_D06:
            {
                s = "CET_D06";
                break;
            }
            case morph::ColourMapType::CET_I3:
            {
                s = "CET_I3";
                break;
            }
            case morph::ColourMapType::CET_D01A:
            {
                s = "CET_D01A";
                break;
            }
            case morph::ColourMapType::CET_L16:
            {
                s = "CET_L16";
                break;
            }
            case morph::ColourMapType::CET_L06:
            {
                s = "CET_L06";
                break;
            }
            case morph::ColourMapType::CET_C2s:
            {
                s = "CET_C2S";
                break;
            }
            case morph::ColourMapType::CET_I1:
            {
                s = "CET_I1";
                break;
            }
            case morph::ColourMapType::CET_C7s:
            {
                s = "CET_C7S";
                break;
            }
            case morph::ColourMapType::CET_I2:
            {
                s = "CET_I2";
                break;
            }
            case morph::ColourMapType::CET_C6s:
            {
                s = "CET_C6S";
                break;
            }
            case morph::ColourMapType::CET_C6:
            {
                s = "CET_C6";
                break;
            }
            case morph::ColourMapType::CET_L05:
            {
                s = "CET_L05";
                break;
            }
            case morph::ColourMapType::CET_D08:
            {
                s = "CET_D08";
                break;
            }
            case morph::ColourMapType::CET_L03:
            {
                s = "CET_L03";
                break;
            }
            case morph::ColourMapType::CET_L14:
            {
                s = "CET_L14";
                break;
            }
            case morph::ColourMapType::CET_C2:
            {
                s = "CET_C2";
                break;
            }
            case morph::ColourMapType::CET_R3:
            {
                s = "CET_R3";
                break;
            }
            case morph::ColourMapType::CET_D01:
            {
                s = "CET_D01";
                break;
            }
            case morph::ColourMapType::CET_C1:
            {
                s = "CET_C1";
                break;
            }
            case morph::ColourMapType::CET_D02:
            {
                s = "CET_D02";
                break;
            }
            case morph::ColourMapType::CET_CBC1:
            {
                s = "CET_CBC1";
                break;
            }
            case morph::ColourMapType::CET_D09:
            {
                s = "CET_D09";
                break;
            }
            case morph::ColourMapType::CET_L10:
            {
                s = "CET_L10";
                break;
            }
            case morph::ColourMapType::CET_R1:
            {
                s = "CET_R1";
                break;
            }
            case morph::ColourMapType::CET_C3:
            {
                s = "CET_C3";
                break;
            }
            case morph::ColourMapType::CET_CBL1:
            {
                s = "CET_CBL1";
                break;
            }
            case morph::ColourMapType::CET_C3s:
            {
                s = "CET_C3S";
                break;
            }
            case morph::ColourMapType::CET_C5s:
            {
                s = "CET_C5S";
                break;
            }
            case morph::ColourMapType::CET_L08:
            {
                s = "CET_L08";
                break;
            }
            case morph::ColourMapType::CET_R4:
            {
                s = "CET_R4";
                break;
            }
            case morph::ColourMapType::CET_R2:
            {
                s = "CET_R2";
                break;
            }
            case morph::ColourMapType::CET_L11:
            {
                s = "CET_L11";
                break;
            }
            case morph::ColourMapType::CET_D10:
            {
                s = "CET_D10";
                break;
            }
            case morph::ColourMapType::CET_D07:
            {
                s = "CET_D07";
                break;
            }
            case morph::ColourMapType::CET_L17:
            {
                s = "CET_L17";
                break;
            }
            case morph::ColourMapType::CET_D12:
            {
                s = "CET_D12";
                break;
            }
            case morph::ColourMapType::CET_CBC2:
            {
                s = "CET_CBC2";
                break;
            }
            case morph::ColourMapType::CET_D13:
            {
                s = "CET_D13";
                break;
            }
            case morph::ColourMapType::CET_D03:
            {
                s = "CET_D03";
                break;
            }
            case morph::ColourMapType::CET_C7:
            {
                s = "CET_C7";
                break;
            }
            case morph::ColourMapType::CET_L07:
            {
                s = "CET_L07";
                break;
            }
            case morph::ColourMapType::CET_L09:
            {
                s = "CET_L09";
                break;
            }
            case morph::ColourMapType::CET_L18:
            {
                s = "CET_L18";
                break;
            }
            case morph::ColourMapType::CET_L19:
            {
                s = "CET_L19";
                break;
            }

            case morph::ColourMapType::Cividis:
            {
                s = "Cividis";
                break;
            }
            case morph::ColourMapType::Viridis:
            {
                s = "Viridis";
                break;
            }
            case morph::ColourMapType::Plasma:
            {
                s = "Plasma";
                break;
            }
            case morph::ColourMapType::Inferno:
            {
                s = "Inferno";
                break;
            }
            case morph::ColourMapType::Magma:
            {
                s = "Magma";
                break;
            }
            case morph::ColourMapType::RainbowZeroWhite:
            {
                s = "RainbowZeroWhite";
                break;
            }
            case morph::ColourMapType::RainbowZeroBlack:
            {
                s = "RainbowZeroBlack";
                break;
            }
            case morph::ColourMapType::Rainbow:
            {
                s = "Rainbow";
                break;
            }
            case morph::ColourMapType::Jet:
            {
                s = "Jet";
                break;
            }
            default:
            {
                break;
            }
            }
            return s;
        }

        //! Return the colour that represents not-a-number
        static std::array<float, 3> nanColour (ColourMapType _t)
        {
            std::array<float, 3> c = {0.0f, 0.0f, 0.0f};
            switch (_t) {
            case ColourMapType::Jet:
            {
                // Red is part of Jet, but purple isn't
                c = {1.0, 0.0f, 1.0f}; // purple
                break;
            }
            case ColourMapType::Rainbow:
            case ColourMapType::RainbowZeroBlack:
            case ColourMapType::Magma:
            {
                c = {1.0, 1.0f, 1.0f}; // white
                break;
            }
            case ColourMapType::RainbowZeroWhite:
            {
                break; // c is { 0, 0, 0 } black
            }
            case ColourMapType::MonochromeRed:
            case ColourMapType::MonovalRed:
            case ColourMapType::DivBlueRed:
            case ColourMapType::CyclicDivBlueRed:
            case ColourMapType::Acton:
            {
                c = {0.0, 1.0f, 0.0f}; // green
                break;
            }
            case ColourMapType::GreyscaleInv:
            {
                // The 'inverted' greyscale tends to white for maximum signal
                c = {1.0, 1.0f, 1.0f};
                break;
            }
            default:
            {
                // Default to red as the NaN colour
                c = {1.0, 0.0f, 0.0f};
                break;
            }
            }

            return c;
        }

        //! How many colour datums does the colour map require?
        static int numDatums (ColourMapType _t, bool _act_2d = false)
        {
            int n = 0;
            switch (_t) {
            case ColourMapType::Trichrome:
            case ColourMapType::RGB:
            case ColourMapType::RGBMono:
            case ColourMapType::RGBGrey:
            {
                n = 3;
                break;
            }
            case ColourMapType::Duochrome:
            case ColourMapType::HSV:
            case ColourMapType::DiscFourWhite:
            case ColourMapType::DiscFourBlack:
            case ColourMapType::DiscSixWhite:
            case ColourMapType::DiscSixBlack:
            {
                n = 2;
                break;
            }
            default: // rest are one datum (unless act_2d is set)
            {
                n = _act_2d ? 2 : 1;
                break;
            }
            }

            return n;
        }
        int numDatums() const { return ColourMap::numDatums (this->type, this->act_2d); }

        //! The maximum number for the range of the datum to convert to a colour. 1 for
        //! floating point variables.
        T range_max = ColourMap<T>::range_max_init();
        static constexpr T range_max_init()
        {
            T rm = T{1};
            // If integral type, might need to change this
            if constexpr (std::is_same<std::decay_t<T>, unsigned char>::value == true) {
                rm = 255;
            } else if constexpr (std::is_same<std::decay_t<T>, char>::value == true) {
                rm = 127;
            } else if constexpr (std::is_same<std::decay_t<T>, bool>::value == true) {
                rm = true;
            } else if constexpr (std::is_same<std::decay_t<T>, unsigned short>::value == true
                                 || std::is_same<std::decay_t<T>, short>::value == true
                                 || std::is_same<std::decay_t<T>, unsigned int>::value == true
                                 || std::is_same<std::decay_t<T>, int>::value == true
                                 || std::is_same<std::decay_t<T>, unsigned long long int>::value == true
                                 || std::is_same<std::decay_t<T>, long long int>::value == true) {
                // For long types, default to 8 bit input; user can change afterwards.
                rm = 255;
            }
            return rm;
        }

        /*!
         * An overload of convert for DuoChrome and HSV ColourMaps
         *
         * Inputs are two datums in the range [0, 1]. For disc colour maps, the datums are
         * considered to be "hue" and "saturation" and are processed through xy_to_radius_angle to
         * convert them into suitable radius and angle values for the colourmap functions.
         *
         * Note that *this will NOT give the full range of possible colours on the disc*!!!
         *
         * What you get is a 'square cut out on the disc', with maximum saturation at the four
         * corners of the square.
         *
         * If you have a colourmapping where you want to use the full colour range (such as mapping
         * radius and angle) use ColourMap::convert_angular, whcih *does* give you access to the
         * full colour disc.
         */
        std::array<float, 3> convert (T _datum1, T _datum2) const
        {
            std::array<float, 3> c = {0.0f, 0.0f, 0.0f};
            if (this->type == ColourMapType::Duochrome) {
                c = this->duochrome (_datum1, _datum2);
            } else if (this->type == ColourMapType::HSV
                || this->type == ColourMapType::DiscFourWhite
                || this->type == ColourMapType::DiscFourBlack
                || this->type == ColourMapType::DiscSixWhite
                || this->type == ColourMapType::DiscSixBlack) {
                // Convert two inputs to radius and angle
                std::array<T, 2> ra = xy_to_radius_angle (_datum1, _datum2);
                float ra0 = static_cast<float>(ra[0]); // radius
                float ra1 = static_cast<float>(ra[1]); // angle

                if (this->type == ColourMapType::HSV) {
                    c = ColourMap::hsv2rgb (ra1, ra0, this->val);
                } else if (this->type == ColourMapType::DiscFourWhite) {
                    // Convert two inputs to output radius and angle, applying rotation as necessary and scaling to range [0,1]
                    lenthe::colormap::disk::four<float> (ra0, ra1, c.data(), true, lenthe::colormap::Sym::None);
                } else if (this->type == ColourMapType::DiscFourBlack) {
                    lenthe::colormap::disk::four<float> (ra0, ra1, c.data(), false, lenthe::colormap::Sym::None);
                } else if (this->type == ColourMapType::DiscSixWhite) {
                    lenthe::colormap::disk::six<float> (ra0, ra1, c.data(), true, lenthe::colormap::Sym::None);
                } else if (this->type == ColourMapType::DiscSixBlack) {
                    lenthe::colormap::disk::six<float> (ra0, ra1, c.data(), false, lenthe::colormap::Sym::None);
                } else {
                    throw std::runtime_error ("ColourMap::convert(T, T): Unexpected case");
                }
            } else if (this->act_2d == true) {
                // Returning a 1D colourmap value using the second dim as saturation
                c = this->convertWithSaturation (_datum1, _datum2);
            } else {
                    throw std::runtime_error ("Set ColourMapType to Duochrome, HSV DiscFourWhite/Black or DiscSixWhite/Black or a 1D map with act_2d=true.");
            }
            return c;
        }

        /*!
         * An overload of convert for HSV/Disc ColourMaps only
         *
         * The inputs are angle in radians and radius in range [0, 1].
         *
         * hue/rotation gets applied in rphi_to_radius_angle.
         */
        std::array<float, 3> convert_angular (T _angle, T _radius) const
        {
            std::array<float, 3> c = {0.0f, 0.0f, 0.0f};

            // ra[1] is angle, ra[0] is radius (magnitude)
            std::array<T, 2> ra = this->rphi_to_radius_angle (_radius, _angle);
            float ra0 = static_cast<float>(ra[0]); // radius
            float ra1 = static_cast<float>(ra[1]); // angle

            if (this->type == ColourMapType::HSV) {
                c = ColourMap::hsv2rgb (ra1, ra0, this->val);
            } else if (this->type == ColourMapType::DiscFourWhite) {
                // Convert two inputs to output radius and angle, applying rotation as necessary and scaling to range [0,1]
                lenthe::colormap::disk::four<float> (ra0, ra1, c.data(), true, lenthe::colormap::Sym::None);
            } else if (this->type == ColourMapType::DiscFourBlack) {
                lenthe::colormap::disk::four<float> (ra0, ra1, c.data(), false, lenthe::colormap::Sym::None);
            } else if (this->type == ColourMapType::DiscSixWhite) {
                lenthe::colormap::disk::six<float> (ra0, ra1, c.data(), true, lenthe::colormap::Sym::None);
            } else if (this->type == ColourMapType::DiscSixBlack) {
                lenthe::colormap::disk::six<float> (ra0, ra1, c.data(), false, lenthe::colormap::Sym::None);
            } else {
                throw std::runtime_error ("Set ColourMapType to HSV or a Disc* map to use ColourMap::convert_angular().");
            }
            return c;
        }

        //! An overload of convert for TriChrome or RGB ColourMaps
        std::array<float, 3> convert (T _datum1, T _datum2, T _datum3) const
        {
            if (this->type != ColourMapType::Trichrome
                && this->type != ColourMapType::RGB
                && this->type != ColourMapType::RGBMono
                && this->type != ColourMapType::RGBGrey) {
                throw std::runtime_error ("Set ColourMapType to Trichrome, RGB, RGBMono or RGBGrey.");
            }
            std::array<float, 3> clr = { 0.0f, 0.0f, 0.0f };
            switch (this->type) {
            case ColourMapType::Trichrome:
            {
                clr = this->trichrome (_datum1, _datum2, _datum3);
                break;
            }
            case ColourMapType::RGBMono:
            {
                clr = this->rgb_to_monochrome (_datum1, _datum2, _datum3);
                break;
            }
            case ColourMapType::RGBGrey:
            {
                clr = this->rgb_to_greyscale (_datum1, _datum2, _datum3);
                break;
            }
            case ColourMapType::RGB:
            default:
            {
                clr = this->rgb (_datum1, _datum2, _datum3);
                break;
            }
            }
            return clr;
        }

        //! A 2D convert() which adjusts the saturation of the retrieved colour using the second dimension.
        std::array<float, 3> convertWithSaturation (const T _datum, const T _saturation) const
        {
            std::array<float, 3> c = this->convert (_datum);
            std::array<float, 3> hsv = ColourMap<T>::rgb2hsv (c);
            float saturation = _saturation > T{1} ? 1.0f : static_cast<float>(_saturation);
            hsv[1] *= saturation < 0.0f ? 0.0f : saturation;
            return ColourMap<T>::hsv2rgb (hsv);
        }

        //! Convert the scalar datum into an RGB (or BGR) colour
        std::array<float, 3> convert (T _datum) const
        {
            float datum = 0.0f;

            // Convert T into a suitable value (with a suitable scaling as necessary) to
            // make the conversion to array<float,3> colour.
            if constexpr (std::is_same<std::decay_t<T>, double>::value == true) {
                // Copy, enforce range
                datum = _datum > T{1} ? 1.0f : static_cast<float>(_datum);
                datum = datum < T{0} ? 0.0f : datum;

            } else if constexpr (std::is_same<std::decay_t<T>, float>::value == true) {
                // Copy, and enforce range of datum
                datum = _datum > T{1} ? 1.0f : _datum;
                datum = datum < T{0} ? 0.0f : datum;

            } else if constexpr (std::is_same<std::decay_t<T>, bool>::value == true) {
                datum = _datum ? 1.0f : 0.0f;

            } else if constexpr (std::is_integral<std::decay_t<T>>::value == true) {
                // For integral types, there's a 'max input range' value
                datum = _datum < 0 ? 0.0f : (float)_datum / static_cast<float>(this->range_max);
                datum = datum > 1.0f ? 1.0f : datum;

            } else {
                throw std::runtime_error ("Unhandled ColourMap data type.");
            }

            std::array<float, 3> c = {0.0f, 0.0f, 0.0f};

            // Check for nan and return a 'nan' colour for the colour map
            if constexpr (std::is_same<std::decay_t<T>, double>::value == true
                          || std::is_same<std::decay_t<T>, float>::value == true) {
                if (std::isnan(datum) == true) { c = ColourMap<T>::nanColour(this->type); return c; }
            }

            switch (this->type) {
            case ColourMapType::Jet:
            {
                // Note that I now give you CET_R4 instead of the jet algorithm
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_R4.size()-1))));
                c = morph::cet::cm_CET_R4[datum_i];

                break;
            }
            case ColourMapType::Rainbow:
            {
                // Note that I now give you CET_C6 instead of the simplistically computed rainbow map
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_C6.size()-1))));
                c = morph::cet::cm_CET_C6[datum_i];
                break;
            }
            case ColourMapType::RainbowZeroBlack:
            {
                if (datum != T{0}) {
                    size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_C6.size()-1))));
                    c = morph::cet::cm_CET_C6[datum_i];
                }
                break;
            }
            case ColourMapType::RainbowZeroWhite:
            {
                if (datum != T{0}) {
                    size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_C6.size()-1))));
                    c = morph::cet::cm_CET_C6[datum_i];
                } else {
                    c = {1.0f, 1.0f, 1.0f};
                }
                break;
            }
            case ColourMapType::Magma:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cm_magma.size()-1))));
                c = morph::cm_magma[datum_i];
                break;
            }
            case ColourMapType::Inferno:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cm_inferno.size()-1))));
                c = morph::cm_inferno[datum_i];
                break;
            }
            case ColourMapType::Plasma:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cm_plasma.size()-1))));
                c = morph::cm_plasma[datum_i];
                break;
            }
            case ColourMapType::Viridis:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cm_viridis.size()-1))));
                c = morph::cm_viridis[datum_i];
                break;
            }
            case ColourMapType::Cividis:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cm_cividis.size()-1))));
                c = morph::cm_cividis[datum_i];
                break;
            }
            case ColourMapType::Twilight:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cm_twilight.size()-1))));
                c = morph::cm_twilight[datum_i];
                break;
            }
            case ColourMapType::Petrov:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cm_petrov.size()-1))));
                c = morph::cm_petrov[datum_i];
                break;
            }

            case ColourMapType::Devon:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_devon.size()-1))));
                c = morph::crameri::cm_devon[datum_i];
                break;
            }
            case ColourMapType::NaviaW:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_naviaW.size()-1))));
                c = morph::crameri::cm_naviaW[datum_i];
                break;
            }
            case ColourMapType::BrocO:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_brocO.size()-1))));
                c = morph::crameri::cm_brocO[datum_i];
                break;
            }
            case ColourMapType::Acton:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_acton.size()-1))));
                c = morph::crameri::cm_acton[datum_i];
                break;
            }
            case ColourMapType::Batlow:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_batlow.size()-1))));
                c = morph::crameri::cm_batlow[datum_i];
                break;
            }
            case ColourMapType::Berlin:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_berlin.size()-1))));
                c = morph::crameri::cm_berlin[datum_i];
                break;
            }
            case ColourMapType::Tofino:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_tofino.size()-1))));
                c = morph::crameri::cm_tofino[datum_i];
                break;
            }
            case ColourMapType::Broc:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_broc.size()-1))));
                c = morph::crameri::cm_broc[datum_i];
                break;
            }
            case ColourMapType::CorkO:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_corkO.size()-1))));
                c = morph::crameri::cm_corkO[datum_i];
                break;
            }
            case ColourMapType::Lapaz:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_lapaz.size()-1))));
                c = morph::crameri::cm_lapaz[datum_i];
                break;
            }
            case ColourMapType::BamO:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_bamO.size()-1))));
                c = morph::crameri::cm_bamO[datum_i];
                break;
            }
            case ColourMapType::Vanimo:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_vanimo.size()-1))));
                c = morph::crameri::cm_vanimo[datum_i];
                break;
            }
            case ColourMapType::Lajolla:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_lajolla.size()-1))));
                c = morph::crameri::cm_lajolla[datum_i];
                break;
            }
            case ColourMapType::Lisbon:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_lisbon.size()-1))));
                c = morph::crameri::cm_lisbon[datum_i];
                break;
            }
            case ColourMapType::GrayC:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_grayC.size()-1))));
                c = morph::crameri::cm_grayC[datum_i];
                break;
            }
            case ColourMapType::Roma:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_roma.size()-1))));
                c = morph::crameri::cm_roma[datum_i];
                break;
            }
            case ColourMapType::Vik:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_vik.size()-1))));
                c = morph::crameri::cm_vik[datum_i];
                break;
            }
            case ColourMapType::Navia:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_navia.size()-1))));
                c = morph::crameri::cm_navia[datum_i];
                break;
            }
            case ColourMapType::Bilbao:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_bilbao.size()-1))));
                c = morph::crameri::cm_bilbao[datum_i];
                break;
            }
            case ColourMapType::Turku:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_turku.size()-1))));
                c = morph::crameri::cm_turku[datum_i];
                break;
            }
            case ColourMapType::Lipari:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_lipari.size()-1))));
                c = morph::crameri::cm_lipari[datum_i];
                break;
            }
            case ColourMapType::VikO:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_vikO.size()-1))));
                c = morph::crameri::cm_vikO[datum_i];
                break;
            }
            case ColourMapType::BatlowK:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_batlowK.size()-1))));
                c = morph::crameri::cm_batlowK[datum_i];
                break;
            }
            case ColourMapType::Oslo:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_oslo.size()-1))));
                c = morph::crameri::cm_oslo[datum_i];
                break;
            }
            case ColourMapType::Oleron:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_oleron.size()-1))));
                c = morph::crameri::cm_oleron[datum_i];
                break;
            }
            case ColourMapType::Davos:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_davos.size()-1))));
                c = morph::crameri::cm_davos[datum_i];
                break;
            }
            case ColourMapType::Fes:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_fes.size()-1))));
                c = morph::crameri::cm_fes[datum_i];
                break;
            }
            case ColourMapType::Managua:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_managua.size()-1))));
                c = morph::crameri::cm_managua[datum_i];
                break;
            }
            case ColourMapType::Glasgow:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_glasgow.size()-1))));
                c = morph::crameri::cm_glasgow[datum_i];
                break;
            }
            case ColourMapType::Tokyo:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_tokyo.size()-1))));
                c = morph::crameri::cm_tokyo[datum_i];
                break;
            }
            case ColourMapType::Bukavu:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_bukavu.size()-1))));
                c = morph::crameri::cm_bukavu[datum_i];
                break;
            }
            case ColourMapType::Bamako:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_bamako.size()-1))));
                c = morph::crameri::cm_bamako[datum_i];
                break;
            }
            case ColourMapType::BatlowW:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_batlowW.size()-1))));
                c = morph::crameri::cm_batlowW[datum_i];
                break;
            }
            case ColourMapType::Nuuk:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_nuuk.size()-1))));
                c = morph::crameri::cm_nuuk[datum_i];
                break;
            }
            case ColourMapType::Cork:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_cork.size()-1))));
                c = morph::crameri::cm_cork[datum_i];
                break;
            }
            case ColourMapType::Hawaii:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_hawaii.size()-1))));
                c = morph::crameri::cm_hawaii[datum_i];
                break;
            }
            case ColourMapType::Bam:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_bam.size()-1))));
                c = morph::crameri::cm_bam[datum_i];
                break;
            }
            case ColourMapType::Imola:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_imola.size()-1))));
                c = morph::crameri::cm_imola[datum_i];
                break;
            }
            case ColourMapType::RomaO:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_romaO.size()-1))));
                c = morph::crameri::cm_romaO[datum_i];
                break;
            }
            case ColourMapType::Buda:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::crameri::cm_buda.size()-1))));
                c = morph::crameri::cm_buda[datum_i];
                break;
            }

            case ColourMapType::Fire:
            {
                lenthe::colormap::ramp::fire<float> (datum, c.data());
                break;
            }
            case ColourMapType::Ocean:
            {
                lenthe::colormap::ramp::ocean<float> (datum, c.data());
                break;
            }
            case ColourMapType::Ice:
            {
                lenthe::colormap::ramp::ice<float> (datum, c.data());
                break;
            }
            case ColourMapType::DivBlueRed:
            {
                lenthe::colormap::ramp::div<float> (datum, c.data());
                break;
            }
            case ColourMapType::CyclicGrey:
            {
                lenthe::colormap::cyclic::gray<float> (datum, c.data());
                break;
            }
            case ColourMapType::CyclicFour:
            {
                lenthe::colormap::cyclic::four<float> (datum, c.data());
                break;
            }
            case ColourMapType::CyclicSix:
            {
                lenthe::colormap::cyclic::six<float> (datum, c.data());
                break;
            }
            case ColourMapType::CyclicDivBlueRed:
            {
                lenthe::colormap::cyclic::div<float> (datum, c.data());
                break;
            }

            case ColourMapType::CET_L02:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_L02.size()-1))));
                c = morph::cet::cm_CET_L02[datum_i];
                break;
            }
            case ColourMapType::CET_L13:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_L13.size()-1))));
                c = morph::cet::cm_CET_L13[datum_i];
                break;
            }
            case ColourMapType::CET_C4:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_C4.size()-1))));
                c = morph::cet::cm_CET_C4[datum_i];
                break;
            }
            case ColourMapType::CET_D04:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_D04.size()-1))));
                c = morph::cet::cm_CET_D04[datum_i];
                break;
            }
            case ColourMapType::CET_L12:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_L12.size()-1))));
                c = morph::cet::cm_CET_L12[datum_i];
                break;
            }
            case ColourMapType::CET_C1s:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_C1s.size()-1))));
                c = morph::cet::cm_CET_C1s[datum_i];
                break;
            }
            case ColourMapType::CET_L01:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_L01.size()-1))));
                c = morph::cet::cm_CET_L01[datum_i];
                break;
            }
            case ColourMapType::CET_C5:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_C5.size()-1))));
                c = morph::cet::cm_CET_C5[datum_i];
                break;
            }
            case ColourMapType::CET_D11:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_D11.size()-1))));
                c = morph::cet::cm_CET_D11[datum_i];
                break;
            }
            case ColourMapType::CET_L04:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_L04.size()-1))));
                c = morph::cet::cm_CET_L04[datum_i];
                break;
            }
            case ColourMapType::CET_CBL2:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_CBL2.size()-1))));
                c = morph::cet::cm_CET_CBL2[datum_i];
                break;
            }
            case ColourMapType::CET_C4s:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_C4s.size()-1))));
                c = morph::cet::cm_CET_C4s[datum_i];
                break;
            }
            case ColourMapType::CET_L15:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_L15.size()-1))));
                c = morph::cet::cm_CET_L15[datum_i];
                break;
            }
            case ColourMapType::CET_L20:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_L20.size()-1))));
                c = morph::cet::cm_CET_L20[datum_i];
                break;
            }
            case ColourMapType::CET_CBD1:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_CBD1.size()-1))));
                c = morph::cet::cm_CET_CBD1[datum_i];
                break;
            }
            case ColourMapType::CET_D06:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_D06.size()-1))));
                c = morph::cet::cm_CET_D06[datum_i];
                break;
            }
            case ColourMapType::CET_I3:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_I3.size()-1))));
                c = morph::cet::cm_CET_I3[datum_i];
                break;
            }
            case ColourMapType::CET_D01A:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_D01A.size()-1))));
                c = morph::cet::cm_CET_D01A[datum_i];
                break;
            }
            case ColourMapType::CET_L16:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_L16.size()-1))));
                c = morph::cet::cm_CET_L16[datum_i];
                break;
            }
            case ColourMapType::CET_L06:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_L06.size()-1))));
                c = morph::cet::cm_CET_L06[datum_i];
                break;
            }
            case ColourMapType::CET_C2s:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_C2s.size()-1))));
                c = morph::cet::cm_CET_C2s[datum_i];
                break;
            }
            case ColourMapType::CET_I1:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_I1.size()-1))));
                c = morph::cet::cm_CET_I1[datum_i];
                break;
            }
            case ColourMapType::CET_C7s:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_C7s.size()-1))));
                c = morph::cet::cm_CET_C7s[datum_i];
                break;
            }
            case ColourMapType::CET_I2:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_I2.size()-1))));
                c = morph::cet::cm_CET_I2[datum_i];
                break;
            }
            case ColourMapType::CET_C6s:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_C6s.size()-1))));
                c = morph::cet::cm_CET_C6s[datum_i];
                break;
            }
            case ColourMapType::CET_C6:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_C6.size()-1))));
                c = morph::cet::cm_CET_C6[datum_i];
                break;
            }
            case ColourMapType::CET_L05:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_L05.size()-1))));
                c = morph::cet::cm_CET_L05[datum_i];
                break;
            }
            case ColourMapType::CET_D08:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_D08.size()-1))));
                c = morph::cet::cm_CET_D08[datum_i];
                break;
            }
            case ColourMapType::CET_L03:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_L03.size()-1))));
                c = morph::cet::cm_CET_L03[datum_i];
                break;
            }
            case ColourMapType::CET_L14:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_L14.size()-1))));
                c = morph::cet::cm_CET_L14[datum_i];
                break;
            }
            case ColourMapType::CET_C2:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_C2.size()-1))));
                c = morph::cet::cm_CET_C2[datum_i];
                break;
            }
            case ColourMapType::CET_R3:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_R3.size()-1))));
                c = morph::cet::cm_CET_R3[datum_i];
                break;
            }
            case ColourMapType::CET_D01:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_D01.size()-1))));
                c = morph::cet::cm_CET_D01[datum_i];
                break;
            }
            case ColourMapType::CET_C1:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_C1.size()-1))));
                c = morph::cet::cm_CET_C1[datum_i];
                break;
            }
            case ColourMapType::CET_D02:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_D02.size()-1))));
                c = morph::cet::cm_CET_D02[datum_i];
                break;
            }
            case ColourMapType::CET_CBC1:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_CBC1.size()-1))));
                c = morph::cet::cm_CET_CBC1[datum_i];
                break;
            }
            case ColourMapType::CET_D09:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_D09.size()-1))));
                c = morph::cet::cm_CET_D09[datum_i];
                break;
            }
            case ColourMapType::CET_L10:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_L10.size()-1))));
                c = morph::cet::cm_CET_L10[datum_i];
                break;
            }
            case ColourMapType::CET_R1:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_R1.size()-1))));
                c = morph::cet::cm_CET_R1[datum_i];
                break;
            }
            case ColourMapType::CET_C3:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_C3.size()-1))));
                c = morph::cet::cm_CET_C3[datum_i];
                break;
            }
            case ColourMapType::CET_CBL1:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_CBL1.size()-1))));
                c = morph::cet::cm_CET_CBL1[datum_i];
                break;
            }
            case ColourMapType::CET_C3s:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_C3s.size()-1))));
                c = morph::cet::cm_CET_C3s[datum_i];
                break;
            }
            case ColourMapType::CET_C5s:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_C5s.size()-1))));
                c = morph::cet::cm_CET_C5s[datum_i];
                break;
            }
            case ColourMapType::CET_L08:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_L08.size()-1))));
                c = morph::cet::cm_CET_L08[datum_i];
                break;
            }
            case ColourMapType::CET_R4:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_R4.size()-1))));
                c = morph::cet::cm_CET_R4[datum_i];
                break;
            }
            case ColourMapType::CET_R2:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_R2.size()-1))));
                c = morph::cet::cm_CET_R2[datum_i];
                break;
            }
            case ColourMapType::CET_L11:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_L11.size()-1))));
                c = morph::cet::cm_CET_L11[datum_i];
                break;
            }
            case ColourMapType::CET_D10:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_D10.size()-1))));
                c = morph::cet::cm_CET_D10[datum_i];
                break;
            }
            case ColourMapType::CET_D07:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_D07.size()-1))));
                c = morph::cet::cm_CET_D07[datum_i];
                break;
            }
            case ColourMapType::CET_L17:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_L17.size()-1))));
                c = morph::cet::cm_CET_L17[datum_i];
                break;
            }
            case ColourMapType::CET_D12:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_D12.size()-1))));
                c = morph::cet::cm_CET_D12[datum_i];
                break;
            }
            case ColourMapType::CET_CBC2:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_CBC2.size()-1))));
                c = morph::cet::cm_CET_CBC2[datum_i];
                break;
            }
            case ColourMapType::CET_D13:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_D13.size()-1))));
                c = morph::cet::cm_CET_D13[datum_i];
                break;
            }
            case ColourMapType::CET_D03:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_D03.size()-1))));
                c = morph::cet::cm_CET_D03[datum_i];
                break;
            }
            case ColourMapType::CET_C7:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_C7.size()-1))));
                c = morph::cet::cm_CET_C7[datum_i];
                break;
            }
            case ColourMapType::CET_L07:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_L07.size()-1))));
                c = morph::cet::cm_CET_L07[datum_i];
                break;
            }
            case ColourMapType::CET_L09:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_L09.size()-1))));
                c = morph::cet::cm_CET_L09[datum_i];
                break;
            }
            case ColourMapType::CET_L18:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_L18.size()-1))));
                c = morph::cet::cm_CET_L18[datum_i];
                break;
            }
            case ColourMapType::CET_L19:
            {
                size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_L19.size()-1))));
                c = morph::cet::cm_CET_L19[datum_i];
                break;
            }


            case ColourMapType::Greyscale:
            {
                // The standard Greyscale Colourmap is best (and matches python Greys
                // colour map) if white means minimum signal and black means maximum
                // signal; hence pass 1-datum to William Lenthe's ramp::gray function.
                lenthe::colormap::ramp::gray<float> (T{1}-datum, c.data());
                break;
            }
            case ColourMapType::GreyscaleInv:
            {
                // The 'inverted' greyscale tends to white for maximum signal
                lenthe::colormap::ramp::gray<float> (datum, c.data());
                break;
            }
            case ColourMapType::Monochrome:
            case ColourMapType::MonochromeRed:
            case ColourMapType::MonochromeBlue:
            case ColourMapType::MonochromeGreen:
            {
                c = this->monochrome (datum);
                break;
            }
            case ColourMapType::Monoval:
            {
                c = this->monoval (datum);
                break;
            }
            case ColourMapType::MonovalRed:
            {
                c = { datum, T{0}, T{0} };
                break;
            }
            case ColourMapType::MonovalBlue:
            {
                c = { T{0}, T{0}, datum };
                break;
            }
            case ColourMapType::MonovalGreen:
            {
                c = { T{0}, datum, T{0} };
                break;
            }
            case ColourMapType::HSV1D:
            {
                // If we're in the upper half range (0.5 to 1.0) then use primary hue, if in lower half range, use hue2
                if (datum >= 0.5f) {
                    c = ColourMap::hsv2rgb (this->hue, 2.0f * (datum - 0.5f), this->val);
                } else { // 0 to 0.5
                    // hue2 should be set to the 'opposite' of hue.
                    c = ColourMap::hsv2rgb (this->hue2, 2.0f * (0.5f - datum), this->val);
                }
                break;
            }
            case ColourMapType::Fixed:
            {
                c = ColourMap::hsv2rgb (this->hue, this->sat, this->val);
                break;
            }
            default:
            {
                break;
            }
            }

            return c;
        }

        //! Getter for type, the ColourMapType of this ColourMap.
        ColourMapType getType() const { return this->type; }

        //! Getter for type, the ColourMapType of this ColourMap, returning as a human-readable string
        std::string getTypeStr() const { return ColourMap::colourMapTypeToStr (this->type); }

        std::string getFlagsStr() const { return ColourMap::colourMapFlagsToStr (this->flags); }

        //! Setter for type, the ColourMapType of this ColourMap.
        void setType (const ColourMapType& tp)
        {
            this->type = tp;
            this->flags = morph::makeColourMapFlags (this->type);
            // Set hue if necessary
            switch (tp) {
            case ColourMapType::MonochromeRed:
            case ColourMapType::MonovalRed:
            {
                this->hue = 1.0f;
                this->val = 1.0f;
                this->sat = 1.0f;
                break;
            }
            case ColourMapType::MonochromeBlue:
            case ColourMapType::MonovalBlue:
            {
                this->hue = 0.667f;
                this->val = 1.0f;
                this->sat = 1.0f;
                break;
            }
            case ColourMapType::MonochromeGreen:
            case ColourMapType::MonovalGreen:
            {
                this->hue = 0.333f;
                this->val = 1.0f;
                this->sat = 1.0f;
                break;
            }
            case ColourMapType::HSV1D:
            {
                this->setHue (0.0f); // sets hue and hue2 (the 'anti' or 'opposite' hue)
                break;
            }
            default:
            {
                break;
            }
            }
        }

        //! Setter that takes a string representation of the colour map type
        void setType (const std::string& ts) { this->setType (ColourMap::strToColourMapType (ts)); }

        //! Set Duochrome to be Red-blue
        void setHueRB()
        {
            if (this->type != ColourMapType::Duochrome) {
                throw std::runtime_error ("red-blue colour map hues only makes sense for ColourMapType::Duochrome");
            }
            this->hue = 0.0f;
            this->hue2 = 0.6667f;
        }
        //! Set Duochrome to be Blue-red
        void setHueBR()
        {
            if (this->type != ColourMapType::Duochrome) {
                throw std::runtime_error ("blue-red colour map hues only makes sense for ColourMapType::Duochrome");
            }
            this->hue = 0.6667f;
            this->hue2 = 0.0f;
        }

        //! Set Duochrome to be Green-Blue
        void setHueGB()
        {
            if (this->type != ColourMapType::Duochrome) {
                throw std::runtime_error ("green-blue colour map hues only makes sense for ColourMapType::Duochrome");
            }
            this->hue = 0.3333f;
            this->hue2 = 0.6667f;
        }
        //! Set Duochrome to be Blue-Green
        void setHueBG()
        {
            if (this->type != ColourMapType::Duochrome) {
                throw std::runtime_error ("blue-green colour map hues only makes sense for ColourMapType::Duochrome");
            }
            this->hue = 0.66667f;
            this->hue2 = 0.3333f;
        }

        //! Set Duochrome to be Red-Green
        void setHueRG()
        {
            if (this->type != ColourMapType::Duochrome) {
                throw std::runtime_error ("red-green colour map hues only makes sense for ColourMapType::Duochrome");
            }
            this->hue = 0.0f;
            this->hue2 = 0.3333f;
        }
        //! Set Duochrome to be Green-Red
        void setHueGR()
        {
            if (this->type != ColourMapType::Duochrome) {
                throw std::runtime_error ("green-red colour map hues only makes sense for ColourMapType::Duochrome");
            }
            this->hue = 0.33333f;
            this->hue2 = 0.0f;
        }

        //! Set up a Cyan-Magenta Duochrome colour scheme
        void setHueCM()
        {
            if (this->type != ColourMapType::Duochrome) {
                throw std::runtime_error ("cyan-magenta colour map hues only makes sense for ColourMapType::Duochrome");
            }
            this->hue = 0.5f;
            this->hue2 = 0.8333f;
        }
        //! Set up a Magenta-Cyan Duochrome colour scheme
        void setHueMC()
        {
            if (this->type != ColourMapType::Duochrome) {
                throw std::runtime_error ("magenta-cyan colour map hues only makes sense for ColourMapType::Duochrome");
            }
            this->hue = 0.83333f;
            this->hue2 = 0.5f;
        }

        //! Set a ColourMapType::Duochrome map using h as the first hue and h+0.3333 as the second hue
        void setDualHue(const float& h)
        {
            if (this->type != ColourMapType::Duochrome) {
                throw std::runtime_error ("Dual-hue colour map hues only makes sense for ColourMapType::Duochrome");
            }
            this->hue = h;
            this->hue2 = h+0.3333f;
            if (hue2 > 1.0f) { hue2 -= 1.0f; }
        }
        //! Set a ColourMapType::DuoChrome map using h as the first hue and h-0.3333 as the second hue
        void setDualAntiHue(const float& h)
        {
            if (this->type != ColourMapType::Duochrome) {
                throw std::runtime_error ("Dual-hue colour map hues only makes sense for ColourMapType::Duochrome");
            }
            this->hue = h;
            this->hue2 = h-0.3333f;
            if (hue2 < 0.0f) { hue2 += 1.0f; }
        }

        //! Set the hue... unless you can't/shouldn't
        void setHue (const float& h)
        {
            switch (this->type) {
            case ColourMapType::MonochromeRed:
            case ColourMapType::MonochromeBlue:
            case ColourMapType::MonochromeGreen:
            case ColourMapType::MonovalRed:
            case ColourMapType::MonovalBlue:
            case ColourMapType::MonovalGreen:
            {
                throw std::runtime_error ("This colour map does not accept changes to the hue");
                break;
            }
            case ColourMapType::Duochrome:
            {
                // Special case duochrome - take first hue, and set second hue to be orthogonal
                this->hue = h;
                this->hue2 = h + 0.3333f;
                if (this->hue2 > 1.0f) { this->hue2 -= 1.0f; }
                break;
            }
            case ColourMapType::Trichrome:
            {
                this->hue = h;
                this->hue2 = h + 0.3333f;
                if (this->hue2 > 1.0f) { this->hue2 -= 1.0f; }
                this->hue3 = h + 0.6667f;
                if (this->hue3 > 1.0f) { this->hue3 -= 1.0f; }
                break;
            }
            case ColourMapType::HSV1D:
            {
                this->hue = h;
                float antihue = this->hue + 0.5f;
                this->hue2 = antihue > 1.0f ? antihue - 1.0f : antihue;
                break;
            }
            default:
            {
                this->hue = h;
                break;
            }
            }
        }

        //! Set the saturation. For many colour maps, this will make little difference,
        //! but for fixed, it allows you to have white, with hue=anything, sat=0, val=1
        void setSat (const float& _s)
        {
            if (this->type != ColourMapType::Fixed && this->type != ColourMapType::Monochrome && this->type != ColourMapType::Monoval) {
                throw std::runtime_error ("Only ColourMapType::Fixed ::Monochrome and ::Monoval allow setting of saturation");
            }
            this->sat = _s;
        }

        //! Set just the colour's value (ColourMapType::Fixed/HSV only)
        void setVal (const float& _v)
        {
            if (this->type != ColourMapType::Fixed && this->type != ColourMapType::HSV
                && this->type != ColourMapType::Monochrome && this->type != ColourMapType::Monoval) {
                throw std::runtime_error ("Only ColourMapType::Fixed ::HSV ::Monochrome and ::Monoval allow setting of value");
            }
            this->val = _v;
        }

        float getHue() const { return this->hue; }
        float getSat() const { return this->sat; }
        float getVal() const { return this->val; }

        //! Set the colour by hue, saturation and value (ColourMapType::Fixed only)
        void setHSV (const float& h, const float& s, const float& v)
        {
            if (this->type != ColourMapType::Fixed && this->type != ColourMapType::Monochrome && this->type != ColourMapType::Monoval) {
                throw std::runtime_error ("Only ColourMapType::Fixed/Monochrome/Monoval allows setting of saturation/value");
            }
            this->hue = h;
            this->sat = s;
            this->val = v;
        }

        //! Set the colour by hue, saturation and value (defined in an array) (ColourMapType::Fixed only)
        void setHSV (const std::array<float,3> hsv) { this->setHSV (hsv[0],hsv[1],hsv[2]); }

        //! Set this->hue, sat and val from the passed in RGB triplet
        void setRGB (const std::array<float,3> rgb)
        {
            std::array<float,3> hsv = ColourMap<T>::rgb2hsv (rgb);
            this->hue = hsv[0];
            this->sat = hsv[1];
            this->val = hsv[2];
        }

        //! Get the hue, in its most saturated form
        std::array<float, 3> getHueRGB() const { return ColourMap::hsv2rgb (this->hue, 1.0f, 1.0f); }

        void setHueRotation (const T rotation_rads)
        {
            if (this->type != ColourMapType::HSV
                && this->type != ColourMapType::DiscFourWhite && this->type != ColourMapType::DiscFourBlack
                && this->type != ColourMapType::DiscSixWhite && this->type != ColourMapType::DiscSixBlack) {
                throw std::runtime_error ("Only ColourMapType::HSV and Disc* allow setting of hue rotation");
            }
            this->hue_rotation = rotation_rads;
        }

        void setHueReverse (const bool rev)
        {
            if (this->type != ColourMapType::HSV
                && this->type != ColourMapType::DiscFourWhite && this->type != ColourMapType::DiscFourBlack
                && this->type != ColourMapType::DiscSixWhite && this->type != ColourMapType::DiscSixBlack) {
                throw std::runtime_error ("It's only relevant to reverse hue direction for ColourMapType::HSV and Disc*");
            }
            this->hue_reverse_direction = rev;
        }

        void set_act_2d (const bool _2d) { this->act_2d = _2d; }

        /*!
         * @param datum gray value from 0.0 to 1.0
         *
         * @returns RGB value in pseudo-jet colormap, which is really CET_R4
         */
        static std::array<float,3> jetcolour (float datum)
        {
            size_t datum_i = static_cast<size_t>( std::abs (std::round (datum * static_cast<float>(morph::cet::cm_CET_R4.size()-1))));
            return morph::cet::cm_CET_R4[datum_i];
        }

        //! HSB to RGB. std::array input/output
        static std::array<float, 3> hsv2rgb (const std::array<float, 3>& hsv)
        {
            return ColourMap<T>::hsv2rgb (hsv[0], hsv[1], hsv[2]);
        }

        //! HSB to RGB. morph::vec input/output
        static morph::vec<float, 3> hsv2rgb_vec (const morph::vec<float, 3>& hsv)
        {
            std::array<float, 3> rgb_ar = ColourMap<T>::hsv2rgb (hsv[0], hsv[1], hsv[2]);
            morph::vec<float, 3> rgb;
            rgb.set_from (rgb_ar);
            return rgb;
        }

        //! Convert hue, saturation, value to RGB. single precision arguments.
        static std::array<float,3> hsv2rgb (float h, float s, float v)
        {
            std::array<float, 3> rgb = { 0.0f, 0.0f, 0.0f };
            int i = floor(h * 6);
            float f = h * 6.0f - i;
            float p = v * (1.0f - s);
            float q = v * (1.0f - f * s);
            float t = v * (1.0f - (1.0f - f) * s);
            switch (i % 6) {
            case 0: rgb[0] = v, rgb[1] = t, rgb[2] = p; break;
            case 1: rgb[0] = q, rgb[1] = v, rgb[2] = p; break;
            case 2: rgb[0] = p, rgb[1] = v, rgb[2] = t; break;
            case 3: rgb[0] = p, rgb[1] = q, rgb[2] = v; break;
            case 4: rgb[0] = t, rgb[1] = p, rgb[2] = v; break;
            case 5: rgb[0] = v, rgb[1] = p, rgb[2] = q; break;
            default: break;
            }
            return rgb;
        }

        //! Convert RGB to HSV, receiving input and returning output as std::array
        static std::array<float, 3> rgb2hsv (const std::array<float, 3>& rgb)
        {
            return ColourMap<T>::rgb2hsv (rgb[0], rgb[1], rgb[2]);
        }

        //! Convert RGB to HSV, receiving input and returning output as morph::vec
        static morph::vec<float, 3> rgb2hsv_vec (const morph::vec<float, 3>& rgb)
        {
            std::array<float, 3> hsv_ar = ColourMap<T>::rgb2hsv (rgb[0], rgb[1], rgb[2]);
            morph::vec<float, 3> hsv;
            hsv.set_from (hsv_ar);
            return hsv;
        }

        //! Convert RGB to hue, saturation, value. single precision arguments.
        static std::array<float, 3> rgb2hsv (float r, float g, float b)
        {
            std::array<float, 3> hsv = { 0.0f, 0.0f, 0.0f };
            float min = 0.0f, max = 0.0f, delta = 0.0f;

            min = r < g ? r : g;
            min = min  < b ? min : b;
            max = r > g ? r : g;
            max = max  > b ? max : b;

            hsv[2] = max; // val
            delta = max - min;
            if (delta < std::numeric_limits<float>::epsilon()) {
                // hsv[1] = 0.0f; // sat
                // hsv[0] = 0.0f; // hue undefined
                return hsv; // already {0,0,max}
            }
            if (max > 0.0f) { // NOTE: if Max is == 0, this divide would cause a crash
                hsv[1] = (delta / max); // sat
            } else {
                // if max is 0, then r = g = b = 0
                // s = 0, h is undefined
                // hsv[1] = 0.0f; // sat
                hsv[0] = std::numeric_limits<float>::quiet_NaN(); // its now undefined
                return hsv;
            }
            if (r < max) {
                if (g < max) {
                    hsv[0] = 4.0f + (r - g) / delta;  // between magenta & cyan
                } else {
                    hsv[0] = 2.0f + (b - r) / delta;  // between cyan & yellow
                }
            } else {
                hsv[0] = (g - b) / delta;  // between yellow & magenta
            }
            hsv[0] *= 60.0f;               // degrees

            if (hsv[0] < 0.0f) { hsv[0] += 360.0f; }

            hsv[0] /= 360.0f; // Finally convert back to a range 0 to 1, compatible with this->hue.

            return hsv;
        }

    private:
        /*!
         * @param datum gray value from 0.0 to 1.0
         *
         * The idea is to range from the value hsv to white. That means as datum nears 0, val should go from val towards 1.0 (max white)
         *
         * @returns RGB value in a mono-colour map, with main colour this->hue; varying saturation.
         */
        std::array<float,3> monochrome (float datum) const
        {
            float _datum = datum * this->sat; // _datum ranges between 0 and this->sat for input [0, 1]
            float _val = this->val + (1.0f - this->val) * (1.0f - datum); // val goes to 1 as input nears 0 (ensuring white)
            return ColourMap::hsv2rgb (this->hue, _datum, _val);
        }

        /*!
         * @param datum gray value from 0.0 to 1.0
         *
         * @returns RGB value in a mono-colour map, with main colour this->hue and varying value.
         */
        std::array<float,3> monoval (float datum) const
        {
            float _datum = datum * this->val;
            return ColourMap::hsv2rgb (this->hue, this->sat, _datum);
        }

        /*!
         * @param datum1 gray value from 0.0 to 1.0
         * @param datum2 gray value from 0.0 to 1.0
         *
         * @returns RGB value in a dual-colour map, with colour this->hue and this->hue2;
         */
        std::array<float,3> duochrome (float datum1, float datum2) const
        {
            std::array<float,3> clr1 = ColourMap::hsv2rgb (this->hue, datum1, datum1);
            std::array<float,3> clr2 = ColourMap::hsv2rgb (this->hue2, datum2, datum2);
            clr1[0] += clr2[0];
            clr1[1] += clr2[1];
            clr1[2] += clr2[2];
            clr1[0] = clr1[0] > 1.0f ? 1.0f : clr1[0];
            clr1[1] = clr1[1] > 1.0f ? 1.0f : clr1[1];
            clr1[2] = clr1[2] > 1.0f ? 1.0f : clr1[2];
            return clr1;
        }

        /*!
         * @param datum1 gray value from 0.0 to 1.0
         * @param datum2 gray value from 0.0 to 1.0
         * @param datum3 gray value from 0.0 to 1.0
         *
         * @returns RGB value in a tri-colour map, with colour this->hue, this->hue2
         * and this->hue3.
         */
        std::array<float,3> trichrome (float datum1, float datum2, float datum3) const
        {
            std::array<float,3> clr1 = ColourMap::hsv2rgb (this->hue, datum1, datum1);
            std::array<float,3> clr2 = ColourMap::hsv2rgb (this->hue2, datum2, datum2);
            std::array<float,3> clr3 = ColourMap::hsv2rgb (this->hue3, datum3, datum3);
            clr1[0] += clr2[0] + clr3[0];
            clr1[1] += clr2[1] + clr3[1];
            clr1[2] += clr2[2] + clr3[2];
            return clr1;
        }

        /*!
         * Convert 'x' and 'y' data values into a radius and angle suitable for a disc colour map
         * such as HSV or DiscSixWhite.
         *
         * x and y inputs are expected to be in range [0, 1]
         *
         * Both returned radius angle are in the range [0,1] (NOT 0, 2pi, for angle), making them
         * suitable for input to ColourMap::hsv2rgb or the lenthe Disc functions.
         *
         * NOTE: The FULL range of colours possible in the colour map cannot be accessed with the
         * radius and angle returned from this function. Imagine a square cookie cutter cutting out
         * from the HSV circle such that at the corners of the square, the saturation is maxiumum.
         *
         * **ColourMap::hue_rotation is applied**, which allows any of the disc colourmaps (HSV,
         * DiscFourWhite etc) to be rotated.
         *
         * Hue reversal is also applied.
         */
        std::array<T, 2> xy_to_radius_angle (T x, T y) const
        {
            // Keep input honest
            x = x > T{1} ? T{1} : x;
            x = x < T{0} ? T{0} : x;
            y = y > T{1} ? T{1} : y;
            y = y < T{0} ? T{0} : y;

            std::array<T, 2> radius_angle;
            // Get the datums centralised about 0 & scale so that the radius is 1 at max
            x = (x - T{0.5}) * morph::mathconst<T>::root_2;
            y = (y - T{0.5}) * morph::mathconst<T>::root_2;

            radius_angle[0] = std::sqrt(x * x + y * y);
            radius_angle[0] = radius_angle[0] > T{1} ? T{1} : radius_angle[0];
            radius_angle[0] = radius_angle[0] < T{0} ? T{0} : radius_angle[0];

            radius_angle[1] = std::atan2 (y, x) + this->hue_rotation;
            radius_angle[1] = radius_angle[1] < T{0} ? radius_angle[1] + morph::mathconst<T>::two_pi : radius_angle[1];
            radius_angle[1] = radius_angle[1] > morph::mathconst<T>::two_pi ? radius_angle[1] - morph::mathconst<T>::two_pi : radius_angle[1];
            radius_angle[1] /= morph::mathconst<T>::two_pi;
            radius_angle[1] = this->hue_reverse_direction ? (T{1} - radius_angle[1]) : radius_angle[1];

            return radius_angle;
        }

        /*!
         * Taking r and phi as inputs, make sure r is in range [0, 1] and convert phi from radians
         * to the range [0,1] representing 0 to 2pi.
         *
         * ColourMap::hue_rotation is applied, which allows any of the disc colourmaps (HSV,
         * DiscFourWhite etc) to be rotated. Hue reversal is also applied.
         */
        std::array<T, 2> rphi_to_radius_angle (T r, T phi) const
        {
            // Get the datums centralised about 0 & scale so that for datum1/2 both equal to 1 we get max saturation
            r = r > T{1} ? T{1} : r;
            r = r < T{0} ? T{0} : r;
            phi += this->hue_rotation;
            phi = phi < T{0} ? phi + morph::mathconst<T>::two_pi : phi;
            phi = phi > morph::mathconst<T>::two_pi ? phi - morph::mathconst<T>::two_pi : phi;
            phi /= morph::mathconst<T>::two_pi;
            phi = this->hue_reverse_direction ? (T{1} - phi) : phi;
            return std::array<T, 2>{r, phi};
        }

        //! Gives same result as convert (datum1, datum2) with the HSV colour map type.
        std::array<float,3> hsv_2d (T datum1, T datum2) const
        {
            std::array<T, 2> ra = this->xy_to_radius_angle (datum1, datum2);
            return ColourMap::hsv2rgb (static_cast<float>(ra[1]), static_cast<float>(ra[0]), this->val);
        }

        /*!
         * Return the colour from the hsv map with the given angle and radius. Takes
         * hue_rotation and hue_reverse_direction into account.
         *
         * This gives the same result as convert_angular (angle, radius) with the HSV colour map type.
         *
         * @param angle_hue An angle in radians (will be modified to lie between 0 and 2pi)
         *
         * @param radius_sat A radius in the range [0, 1]
         */
        std::array<float,3> hsv_anglerad (T angle_hue, T radius_sat) const
        {
            std::array<T, 2> ra = this->rphi_to_radius_angle (radius_sat, angle_hue);
            return ColourMap::hsv2rgb (static_cast<float>(ra[1]), static_cast<float>(ra[0]), this->val);
        }

        //! Bounds-check three datums to be in range [0,1]
        static void bounds_check_3 (float& datum1, float& datum2, float& datum3)
        {
            datum1 = datum1 > 1.0f ? 1.0f : datum1;
            datum2 = datum2 > 1.0f ? 1.0f : datum2;
            datum3 = datum3 > 1.0f ? 1.0f : datum3;
            datum1 = datum1 < 0.0f ? 0.0f : datum1;
            datum2 = datum2 < 0.0f ? 0.0f : datum2;
            datum3 = datum3 < 0.0f ? 0.0f : datum3;
        }

        //! A pass-through with bounds checking
        static std::array<float,3> rgb (float datum1, float datum2, float datum3)
        {
            ColourMap::bounds_check_3 (datum1, datum2, datum3);
            std::array<float,3> clr = {datum1, datum2, datum3};
            return clr;
        }

        //! RGB to monochrome, to display RGB values in monochrome (using this->hue)
        std::array<float,3> rgb_to_monochrome (float datum1, float datum2, float datum3) const
        {
            ColourMap::bounds_check_3 (datum1, datum2, datum3);
            // Get monochrome colour for the greyscale datum
            float datum = 0.299f * datum1 + 0.587f * datum2 + 0.114 * datum3;
            return this->monochrome (datum);
        }

        //! RGB to greyscale, to display RGB values in a greyscale
        std::array<float,3> rgb_to_greyscale (float datum1, float datum2, float datum3) const
        {
            ColourMap::bounds_check_3 (datum1, datum2, datum3);
            // Get greyscale colour for the mean datum according to
            // https://docs.opencv.org/3.4/de/d25/imgproc_color_conversions.html
            float datum = 0.299f * datum1 + 0.587f * datum2 + 0.114 * datum3;
            return greyscale (datum);
        }

        /*!
         * @param datum gray value from 0.0 to 1.0
         *
         * @returns Generate RGB value for which all entries are equal and the
         * brightness gives the map value. Thus, \a datum = 1 gives white and \a datum =
         * 0 gives black.
         */
        std::array<float,3> greyscale (float datum) const
        {
            return ColourMap::hsv2rgb (this->hue, 0.0f, datum);
            // or
            //return {datum, datum, datum}; // assuming 0 <= datum <= 1
        }

        //! A colour map which is a rainbow through the colour space, varying the hue.
        std::array<float,3> rainbow (float datum) const
        {
            return ColourMap::hsv2rgb (datum, 1.0f, 1.0f);
        }
    };

} // namespace morph
