Colour Maps

Colour maps, also referred to as legends in the software, associate colours with numeric ranges or string values. Colour maps can be applied to objects of most spatial data types to highlight physical trends or attributes. You can use the Maptek Python SDK to access legends created in the software, or programmatically create colour maps that can be used in scripts, or made available to users of the software. An example of doing this is provided in the Create facet primitive attributes example.

Colour map examples

Creating a string colour map

In the following code, we:

  • Create a string colour map

  • Create a dense block model

  • Apply the string colour map to the dense block model using block attributes

"""Example 1: Colouring a dense block model using a string colour map."""

from mapteksdk.project import Project
from mapteksdk.data import StringColourMap, DenseBlockModel

project = Project()

with project.new_or_edit("legends/string_colour_map", StringColourMap) as string_map:
  # The names associated with the colours in the colour map. Note that these names are case-sensitive.
  # ie: red is valid, but Red is an invalid.
  string_map.legend = ["red", "green", "blue", "yellow", "cyan", "magenta", "grey", "white"]
  string_map.colours = [[255, 0, 0], # red
                     [0, 255, 0], # green
                     [0, 0, 255], # blue
                     [255, 255, 0], # yellow
                     [0, 255, 255], # cyan
                     [255, 0, 255], # magenta
                     [100, 100, 100], # black
                     [255, 255, 255]] # gray
  
  # cutoff specifies how to colour invalid values. In this case invalid values will be coloured
  # transparent.
  string_map.cutoff = [0, 0, 0, 0]

# Now use the colour map to colour a block model.
with project.new("blockmodels/coloured_block_model", DenseBlockModel(
    x_res=1, y_res=1, z_res=1, x_count=3, y_count=3, z_count=1
    )) as coloured_model:
  # First a block attribute must be created to colour by.
  # Note that the block in the middle has an invalid value so will be invisible.
  coloured_model.block_attributes["colours"] = [
    "red", "green", "blue",
    "yellow", "invalid", "cyan",
    "magenta", "grey", "white",
    ]
  # Now that the attribute has been added, we can assign the colour map to it.
  coloured_model.block_attributes.set_colour_map("colours", string_map)

If you run this code, you will create a string colour map and a coloured dense block model that look like the following:

Creating a numeric colour map

In the following code, we:

  • Create a solid numeric colour map

  • Create a dense block model

  • Apply the solid numeric colour map to the dense block model

"""Example 2: Colouring a dense block model using a solid numeric colour map.

There are two types of numeric colour map - solid and interpolated. They are
differentiated by the interpolated property of the colour map.

"""
from mapteksdk.project import Project
from mapteksdk.data import NumericColourMap, DenseBlockModel

project = Project()

with project.new_or_edit("legends/numeric_map_exp", NumericColourMap) as solid_map:
  solid_map.interpolated = False
  # For non-interpolated (solid) colour maps there is one less colour than range.
  solid_map.ranges = [0, 10, 20, 30, 40, 50, 60, 70]
  solid_map.colours = [[255, 0, 0], # 0 < value <= 10 are coloured this colour (Red).
                       [0, 255, 0], # 10 < value <= 20 are coloured this colour (Green).
                       [0, 0, 255], # 20 < value < 30 are coloured this colour (Blue).
                       [255,50,100], # 30
                       [250,255,40], # 40
                       [100, 30, 255], #50
                       [45, 45, 45],  #60
                      ]

  # Colours below zero are coloured this colour. In this case it is semi-transparent red.
  solid_map.lower_cutoff = [255, 0, 0, 100]

  # Colours greater than the highest range, in this case it is semi-transparent blue.
  solid_map.upper_cutoff = [0, 0, 255, 100]

with project.new("blockmodels/dense_solid_colours", DenseBlockModel(
    x_count=5, y_count=1, z_count=1, x_res=1, y_res=1, z_res=1
    ), overwrite=True) as solid_model:
  solid_model.block_attributes["solid_colour"] = [-5, 5, 15, 25, 35]
  solid_model.block_attributes.set_colour_map("solid_colour", solid_map)

If you run the following code, you will create a solid numeric colour map and dense block model that look like this:

Applying a colour map to an object

In the following code, we:

  • Create an interpolated numeric colour map

  • Create a dense block model

  • Apply the interpolated numeric colour map to the dense block model

"""Example 4: Colouring a block model using a numeric colour map."""

from mapteksdk.project import Project
from mapteksdk.data import NumericColourMap, DenseBlockModel, Surface

import numpy as np

project = Project()

with project.new_or_edit("legends/interpolated_map", NumericColourMap) as interpolated_map:
  interpolated_map.interpolated = True
  # For interpolated colour maps the number of colours is equal to the number of ranges.
  interpolated_map.ranges = [0, 10, 20]
  interpolated_map.colours = [[255, 0, 0],
                              # Colours between 0 and 10 will smoothly transition from the colour above (red)
                              # to the colour below (green)
                              [0, 255, 0],
                              # Colours between 10 and 20 will smoothly transition from the colour above (green)
                              # to the colour below (blue)
                              [0, 0, 255],
                             ]

  # Colours below zero are coloured this colour. In this case it is semi-transparent red.
  interpolated_map.lower_cutoff = [255, 0, 0, 100]

  # Colours greater than the highest range, in this case it is semi-transparent blue.
  interpolated_map.upper_cutoff = [0, 0, 255, 100]

# Use the colour map to colour a blockmodel.
with project.new("blockmodels/dense_interpolated_colours", DenseBlockModel(
    x_count=11, y_count=1, z_count=1, x_res=1, y_res=1, z_res=1
    ), overwrite=True) as interpolated_model:
  # Create a block attribute to colour by.
  # From left to right when viewing from above:
  # The first block's value is -1 which is below the minimum so is coloured by lower_offcut.
  # As the value increases from 2 to 10 the blocks get progressively more green.
  # As the value increases from 10 to 18 the blocks get progressively more blue.
  # And finally the last block's value is 22 which is above the maximum so is coloured by upper_offcut.
  interpolated_model.block_attributes["interpolated_colour"] = [-1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 22]
  interpolated_model.block_attributes.set_colour_map("interpolated_colour", interpolated_map)

If you run the following code, you will create an interpolated NumericColourMap and DenseBlockModel that look like this:

Accessing a colour map like a dictionary

The colours of string colour maps can be accessed as if the object was a dictionary of colours. The script in the following example uses this property to create a colour map in a more readable way:

from mapteksdk.project import Project
from mapteksdk.data import StringColourMap
 
if __name__ == "__main__":
  with Project() as project:
    with project.new("legends/dictionary_map", StringColourMap) as colour_map:
      colour_map["red"] = [255, 0, 0, 255]
      colour_map["green"] = [0, 255, 0, 255]
      colour_map["blue"] = [0, 0, 255, 255]
      colour_map["yellow"] = [255, 255, 0, 255]
      colour_map["magenta"] = [255, 0, 255, 255]
      colour_map["cyan"] = [0, 255, 255, 255]
      # The cut off colour is transparent grey.
      colour_map.cutoff = [100, 100, 100, 100]

The script above creates the following colour map in the application:

String colour maps support the full dictionary interface. Thus they support the following operations:

# This script is a fragment and cannot run on its own.
# Get the colour associated with red.
# This would raise an error if red is not in the colour map.
red = colour_map["red"]
 
# The get function will return the cut off if the colour doesn't exist.
orange = colour_map.get("orange")
 
# Del can be used to delete colours from the map.
del colour_map["red"]
 
# It is also possible to check if a key exists in the colour map.
if "red" in colour_map:
    print("No red in this colour map...")

Colour map case sensitivity

The following script has a problem:

from mapteksdk.project import Project
from mapteksdk.data import StringColourMap, DenseBlockModel, ObjectID
from mapteksdk.operations import open_new_view

def create_dense_block_model_with_attribute(
        path: str,
        target_project: Project) -> ObjectID[DenseBlockModel]:
    """Creates a dense block model with the 'Type' block attribute.

    This block attribute contains strings that have differing casing.

    Parameters
    ----------
    path
        Path to create the block model at.
    target_project
        Project to use to create the block model.

    Returns
    -------
    ObjectID
        Object ID of the newly created block model.
    """
    with target_project.new(
            path, DenseBlockModel(
                row_count=3, col_count=4, slice_count=1,
                row_res=0.5, col_res=0.5, slice_res=1.5
            )) as model:
        model.block_attributes["Type"] = [
            "IRON", "Iron", "iron", "IRon",
            "COPPER", "Copper", "copper", "COpper",
            "COBALT", "Cobalt", "cobalt", "CoBalt"
        ]
        return model.id

if __name__ == "__main__":
    with Project() as project:
        block_id = create_dense_block_model_with_attribute(
            "block models/examples/case_sensitive",
            project)
        with project.edit(block_id) as edit_model:
            with project.new(None, StringColourMap) as colour_map:
                colour_map.case_sensitive = True
                colour_map["IRON"] = [255, 0, 0, 255]
                colour_map["copper"] = [0, 255, 0, 255]
                colour_map["Cobalt"] = [0, 0, 255, 255]
                colour_map.cutoff = [100, 100, 100, 255]
            edit_model.block_attributes.set_colour_map("Type", colour_map)
        open_new_view([block_id])

This script creates the following block model:

\

The blocks in the bottom row should be all red, the blocks in the middle row should be all green and the blocks in the top row should be all blue. But instead most of the blocks are grey, indicating the block attribute value is not in the colour map.

The cause is quite clear by comparing the keys in the colour map to the block attribute values.

The colour map contains the following keys:

Key Colour
IRON Red
copper Green
Cobalt Blue

and the block model contains the following values:

COBALT Cobalt cobalt CoBalt
COPPER Copper copper COpper
IRON Iron iron IRon

Only three of the twelve values for the block attribute match the casing of the attribute in the colour map, so only three are coloured using the colour map. The rest are coloured using the cut-off colour. Why? Because the colour map is case sensitive. It does not consider the strings “IRON” and “Iron” to be the same. This is a common problem for drillhole databases, especially when strings are entered manually by many different people.

To cause a colour map to be case insensitive, the script needs to set the StringColourMap.case_sensitive flag to False. Below is the same script, but with the case sensitive flag set to False.

from mapteksdk.project import Project
from mapteksdk.data import StringColourMap, DenseBlockModel, ObjectID
from mapteksdk.operations import open_new_view

def create_dense_block_model_with_attribute(
        path: str,
        target_project: Project) -> ObjectID[DenseBlockModel]:
    """Creates a dense block model with the 'Type' block attribute.

    This block attribute contains strings that have differing casing.

    Parameters
    ----------
    path
        Path to create the block model at.
    target_project
        Project to use to create the block model.

    Returns
    -------
    ObjectID
        Object ID of the newly created block model.
    """
    with target_project.new(
            path, DenseBlockModel(
                row_count=3, col_count=4, slice_count=1,
                row_res=0.5, col_res=0.5, slice_res=1.5
            )) as model:
        model.block_attributes["Type"] = [
            "IRON", "Iron", "iron", "IRon",
            "COPPER", "Copper", "copper", "COpper",
            "COBALT", "Cobalt", "cobalt", "CoBalt"
        ]
        return model.id

if __name__ == "__main__":
    with Project() as project:
        block_id = create_dense_block_model_with_attribute(
            "block models/examples/case_insensitive",
            project)
        with project.edit(block_id) as edit_model:
            with project.new(None, StringColourMap) as colour_map:
                colour_map.case_sensitive = False
                colour_map["IRON"] = [255, 0, 0, 255]
                colour_map["copper"] = [0, 255, 0, 255]
                colour_map["Cobalt"] = [0, 0, 255, 255]
                colour_map.cutoff = [100, 100, 100, 255]
            edit_model.block_attributes.set_colour_map("Type", colour_map)
        open_new_view([block_id])

This script creates the following block model:

Because the colour map is case insensitive, the block model is coloured correctly regardless of the casing of the strings.

Note
  • Data containing inconsistent casing is common when data is entered manually.

  • Some people will prefer different casing (e.g. “IRON”, “iron”, “Iron”)

  • There is also the possibility of typos due to holding down the Shift key too long (“IRon”).

  • An alternative to making the colour map case sensitive is to fix the data using a script that makes all of the values consistently cased.

  • A case sensitive colour map can have different colours for different cases of the same key (e.g. “iron”, “Iron” and “IRON” could be coloured differently).

  • A case-insensitive colour map considers all keys that only differ by case to be identical (e.g. “iron”, “Iron”, “IRON” are all considered the same key).