#include <morph/ColourMap.h>
Introduction
Colour is one of the most important ways you can indicate value in a visualization. morph::ColourMap
is the class that provides many mappings of values to graded colours.
The class ColourMap
exists to provide the function of converting an input number (usually a float
) in the range [0, 1] into an RGB colour triplet, returned as (usually) std::array<float, 3>
. The examples below show several colour maps which will be familiar to those who have used colour maps in Python or MATLAB (the maps are shown as morph::ColourBarVisual objects).
Morphologica now includes a wide variety of colour maps, most of which are perceptually uniform. Thanks to Fabio Crameri, William Lenthe and the teams at CET and Matplotlib for providing open source code and tables for these maps.
As well as the one dimensional colour maps shown above, morph::ColourMap
can convert two (and three) dimensional numbers into colours. Here you can see i) the ‘HSV’ map which converts the ‘x’ and ‘y’ of a two-dimensional input into polar coordinates, then uses these as the hue (r) and saturation (phi) of an HSV colour specification and ii) two ‘Duochrome’ maps that encode two dimensions of data as complementary colours. Here, we’re encoding the 2D coordinate on a square grid into colour.
A ColourMap
is often a member of a VisualModel-derived class. For example, the HexGridVisual
class has a ColourMap
which is used to select colours for each hex in the grid. These are coloured with the ‘plasma’ colour map:
Available maps
The full list of available maps is found in the enumerated class morph::ColourMapType
, found in ColourMap.h:
enum class ColourMapType // in morph namespace
{
Jet,
Rainbow,
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.
Magma, // Like matplotlib's magma
Inferno, // matplotlib's inferno
Plasma, // etc
Viridis,
Cividis,
Twilight,
Greyscale, // Greyscale is any hue; saturation=0; *value* varies. High signal (datum ->1) gives dark greys to black
GreyscaleInv, // Inverted Greyscale. High signal gives light greys to white
Monochrome, // Monochrome is 'monohue': fixed hue; vary the *saturation* with value fixed at 1.
MonochromeRed,
MonochromeBlue,
MonochromeGreen,
Monoval, // Monoval varies the *value* of the colour
MonovalRed,
MonovalBlue,
MonovalGreen,
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.
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.
// Map the negative portion of our input to the rnage 0->0.5 and the positive to 0.5->1
Fixed // Fixed colour. Should return same colour for any datum. User must set hue, sat, val.
};
All of these maps are one dimensional, converting a scalar (usually in the range [0, 1]) to a colour. Exceptions are Duochrome
, Trichrome
, RGB
, RGBMono
, RGBGrey
and HSV
which convert 2 or 3 dimensional vectors into a colour. Lastly, Fixed
is a special case. It’s a colour map which always returns the same colour, but it will pretend to be a 1D map.
Client coding with ColourMap
Constructors and ColourMapType
ColourMap
is a templated class whose template argument T
is the type of the data values. T
may be any floating point or integer type.
template <typename T>
class ColourMap
{ /* ... */ }
Construct with a ColourMapType
as argument, with a string representation of the colour map type or with no argument:
// ColourMap::type will default to ColourMapType::Plasma
morph::ColourMap<float> colour_map1;
// Explicitly choose ColourMapType::Plasma:
morph::ColourMap<float> colour_map2(morph::ColourMapType::Plasma);
// Choose using the 'string name':
morph::ColourMap<float> colour_map3("plasma");
The string name is always a lower-case version of the ColourMapType name. Plasma -> “plasma”; GreyscaleInv -> “greyscaleinv” and so on. A ColourMapType
can be obtained for a given string name with the static function ColourMap::strToColourMapType
(and back again):
morph::ColourMapType cmtype = morph::ColourMap::strToColourMapType ("jet");
std::string cmtype_str = morph::ColourMap::colourMapTypeToStr (cmtype);
You can check the number of datums that the map requires with
int ndatums = morph::ColourMap::numDatums (morph::ColourMapType::HSV); // returns 2
or
morph::ColourMap<float> colour_map1 (morph::ColourMapType::Twilight);
int ndatums = colour_map1.numDatums(); // returns 1
You can set the type after construction and getType() at any time:
colour_map1.setType (morph::ColourMapType::Jet);
colour_map1.setType (std::string("jet"));
auto thetype = colour_map1.getType();
std::string string_type = colour_map1.getTypeStr();
Accessing colours with convert()
Access a colour from the map using the convert
functions:
// 1D input maps:
morph::ColourMap<float> colour_map1 (morph::ColourMapType::Viridis);
std::array<float, 3> mycolour1 = colour_map1.convert (0.5f);
// 2D input maps:
morph::ColourMap<float> colour_map2 (morph::ColourMapType::Duochrome);
std::array<float, 3> mycolour2 = colour_map2.convert (0.5f, 0.3f);
// 3D input maps:
morph::ColourMap<float> colour_map3 (morph::ColourMapType::RGBGrey);
std::array<float, 3> mycolour3 = colour_map3.convert (0.5f, 0.3f, 0.2f);
If you choose an incompatible convert
function (such as convert(float, float) for a 1D ColourMapType) then a runtime error will be thrown.
Choice of template type T
The examples above show instances of morph::ColourMap<T>
with T=float
which is most commonly used. double
may also be used. When the ColourMap
template type T
is floating point, inputs should be given in the range [0, 1].
It is also possible to use integral types with ColourMap
. In these cases the range of the input can be between 0 and the maximum value for the type or 255, whichever is smaller.
You can obtain the maximum value of the range with the T ColourMap::range_max
variable.
morph::ColourMap<char> cm_char;
std::array<float, 3> max_colour = cm_char.convert (127);
std::array<float, 3> min_colour = cm_char.convert (0);
std::cout << cm_char.range_max << "\n"; // 127
morph::ColourMap<unsigned char> cm_unsigned_char;
max_colour = cm_unsigned_char.convert (255);
min_colour = cm_unsigned_char.convert (0);
std::cout << cm_unsigned_char.range_max << "\n"; // 255
// Anything with >8 bits uses range [0 255]. Pull requests to change this would be welcome.
morph::ColourMap<unsigned int> cm_unsigned_int;
max_colour = cm_unsigned_int.convert (255);
min_colour = cm_unsigned_int.convert (0);
std::cout << cm_unsigned_int.range_max << "\n"; // 255
// You can even ColourMap<bool>!
morph::ColourMap<bool> cm_bool;
max_colour = cm_bool.convert (true);
min_colour = cm_bool.convert (false);
std::cout << cm_bool.range_max << "\n"; // 1
Using 1D maps
One dimensional maps, which convert a scalar value into a colour are easy to use. In most cases, you construct a ColourMap<T>
object, set its ColourMapType
and then call ColourMap::convert(T)
.
Importantly, all the 1D maps use the same input range for the mapping. This is [0, 1] for floating point types. Your data may well fall into a different range. For this reason, a ColourMap
is usually used along with a morph::Scale
object. You set the Scale
so that your input data is mapped to a range [0, 1] (or [0, 127] for T=char
/[0, 255] for other integral types).
Variable hue in 1D maps
The Monochrome
map has a gradually increasing saturation of one colour. The colour, which defaults to red, is held in the private member T ColourMap::hue
and can be changed with setHue
. Monochrome
simply wires the input datum to the saturation and creates an HSV colour based on ( ColourMap::hue
, input datum, ColourMap::value
). ColourMap::value
is 1 by default, but it can be set with setVal()
.
morph::ColourMap<float> colour_map1 (morph::ColourMapType::Monochrome);
colour_map1.setHue (0.3f); // Range [0, 1]. See rainbow map for the hue mapping. 0.3 gives green.
std::array<float, 3> mycolour1 = colour_map1.convert (0.5f);
This image shows six Monochrome
colour maps with varying hues
There are some convenience ColourMapTypes that set the hue when you set the type. These are MonochromeRed
, MonochromeGreen
and MonochromeBlue
which set the hue to 0, 0.333 and 0.667, respectively.
The Monoval
maps are similar to Monochrome
, but instead of varying saturation, they vary the value of the HSV colour. They transition from black to the maximally bright colour. They look like this:
HSV1D
HSV1D
is a one dimensional hue-saturation-value map that is related to the two dimensional ColourMapType::HSV
. The hue set for a ColourMap
of type HSV1D
defines the angle on the HSV wheel at which the linear map will be taken. Although these maps are useful for values which are both positive and negative, they still accept the same input range ([0, 1], [0 127 or [0 255]) as the other colour maps.
Using 2D ColourMaps
Duochrome
, HSV
.
Static colour conversion methods
ColourMap contains some static methods that could be used in isolation in your programs.
// Convert float ([0, 1]) into a colour from the Jet colour map
static std::array<float,3> jetcolour (float datum);
// The Hue-Saturation-Value to Red-Green-Blue conversion
static std::array<float, 3> hsv2rgb (const std::array<folat, 3>& hsv);
static std::array<float, 3> hsv2rgb (float h, float s, float v);
static morph::vec<float, 3> hsv2rgb_vec (const morph::vec<float, 3>& hsv);
// RGB to HSV
static std::array<float, 3> rgb2hsv (float r, float g, float b);