"""
Relief Visualization Toolbox – Visualization Functions
Contains classes and methods for blending.
Credits:
Žiga Kokalj (ziga.kokalj@zrc-sazu.si)
Krištof Oštir (kristof.ostir@fgg.uni-lj.si)
Klemen Zakšek
Peter Pehani
Klemen Čotar
Maja Somrak
Žiga Maroh
Nejc Čož
Copyright:
2010-2020 Research Centre of the Slovenian Academy of Sciences and Arts
2016-2020 University of Ljubljana, Faculty of Civil and Geodetic Engineering
"""
# TODO: more testing, find and fix bugs if they exists
import datetime
import json
import os
import warnings
import numpy as np
import rvt.default
import rvt.vis
from rvt.blend_func import *
[docs]
def create_blender_file_example(file_path=None):
"""Create blender .json file example (can be changed and read). Example is VAT - Archaeological combination"""
data = {"combination": {"name": "VAT - Archaeological",
"layers":
[
{"layer": "1", "visualization_method": "Sky-View Factor", "norm": "Value",
"min": 0.7, "max": 1.0,
"blend_mode": "Multiply", "opacity": 25},
{"layer": "2", "visualization_method": "Openness - Positive",
"norm": "Value", "min": 68, "max": 93,
"blend_mode": "Overlay", "opacity": 50},
{"layer": "3", "visualization_method": "Slope gradient", "norm": "Value",
"min": 0, "max": 50,
"blend_mode": "Luminosity", "opacity": 50},
{"layer": "4", "visualization_method": "Hillshade", "norm": "Value",
"min": 0, "max": 1,
"blend_mode": "Normal", "opacity": 100},
{"layer": "5", "visualization_method": "None"}
]
}}
if file_path is None:
file_path = r"settings\blender_file_example.json"
if os.path.isfile(file_path):
pass
else:
if not os.path.exists(os.path.dirname(file_path)):
os.makedirs(os.path.dirname(file_path))
dat = open(file_path, "w")
dat.write(json.dumps(data, indent=4))
dat.close()
[docs]
class BlenderLayer:
"""
Class which define layer for blending. BlenderLayer is basic element in BlenderCombination.layers list.
Attributes
----------
vis : str
Visualization method.
normalization : str
Normalization type, could be "Value" or "Percent".
min : float
Normalization minimum.
max : float
Normalization maximum.
opacity : integer
Image (visualization) opacity.
colormap : str
Colormap form matplotlib (https://matplotlib.org/3.3.2/tutorials/colors/colormaps.html).
min_colormap_cut : float
What lower part of colormap to cut to select part of colormap.
Valid values are between 0 and 1, if 0.2 it cuts off (deletes) 20% of lower colors in colormap.
If None cut is not applied.
max_colormap_cut : float
What upper part of colormap to cut to select part of colormap.
Valid values are between 0 and 1, if 0.8 it cuts off (deletes) 20% of upper colors in colormap.
If None cut is not applied.
image_path : str
Path to DEM. Doesn't matter if image is not None. Leave None if you would like for blender to compute it.
image : numpy.array (2D)
Visualization raster. Leave None if you would like for blender to compute it.
"""
def __init__(self, vis_method=None, normalization="value", minimum=None, maximum=None,
blend_mode="normal", opacity=100, colormap=None, min_colormap_cut=None, max_colormap_cut=None,
image=None, image_path=None):
self.vis = vis_method
self.normalization = normalization
self.min = minimum
self.max = maximum
self.blend_mode = blend_mode
self.opacity = opacity
self.colormap = colormap
self.min_colormap_cut = min_colormap_cut
self.max_colormap_cut = max_colormap_cut
self.image_path = image_path
self.image = image
[docs]
def check_data(self):
""" Check Attributes """
if self.normalization == "percent":
self.normalization = "perc"
if self.vis is None: # if visualization is None everything is None
self.normalization = None
self.min = None
self.max = None
self.blend_mode = None
self.opacity = None
self.colormap = None # leave None if you don't want to apply colormap to grayscale
self.min_colormap_cut = None # if None it equals to 0
self.max_colormap_cut = None # if None it equals to 1
self.image = None # leave None if you wish for blender to compute visualization
self.image_path = None # leave None if you wish for blender to compute visualization
else:
if not (self.normalization.lower() == "value" or self.normalization.lower() == "perc"):
raise Exception("rvt.blend.BlenderLayer.check_data: normalization value incorrect!")
if self.normalization.lower() == "perc" and (self.min + self.max) >= 100:
raise Exception("rvt.blend.BlenderLayer.check_data: when normalization is perc, min + max has "
"to smaller then 100%!")
if self.min > self.max and self.normalization.lower() == "value":
raise Exception("rvt.blend.BlenderLayer.check_data: min bigger than max!")
if self.blend_mode.lower() != "normal" and self.blend_mode.lower() != "multiply" and \
self.blend_mode.lower() != "overlay" and self.blend_mode.lower() != "luminosity" and \
self.blend_mode.lower() != "screen" and self.blend_mode.lower() != "soft_light":
raise Exception("rvt.blend.BlenderLayer.check_data: blend_mode incorrect!")
if 0 > self.opacity > 100:
raise Exception("rvt.blend.BlenderLayer.check_data: opacity incorrect [0-100]!")
if self.colormap is None and (self.min_colormap_cut is not None or self.max_colormap_cut is not None):
raise Exception("rvt.blend.BlenderLayer.check_data: colormap not defined but min_colormap_cut or "
"max_colormap_cut are!")
if self.image is None and self.image_path is None:
if self.vis.lower() != "slope gradient" and self.vis.lower() != "hillshade" and \
self.vis.lower() != "shadow" and self.vis.lower() != "multiple directions hillshade" and \
self.vis.lower() != "simple local relief model" and self.vis.lower() != "sky-view factor" and \
self.vis.lower() != "anisotropic sky-view factor" and \
self.vis.lower() != "openness - positive" and self.vis.lower() != "openness - negative" and \
self.vis.lower() != "sky illumination" and self.vis.lower() != "local dominance" and \
self.vis.lower() != "multi-scale relief model" and \
self.vis.lower() != "multi-scale topographic position":
raise Exception("rvt.blend.BlenderLayer.check_data: Incorrect vis, if you don't input image or "
"image_path you have to input known visualization method (vis)!")
[docs]
class BlenderCombination:
"""
Class for storing layers (rasters, parameters for blending) and rendering(blending) into blended raster.
Attributes
----------
dem_arr : np.array (2D)
Array with DEM data, needed for calculating visualization functions in memory.
dem_path : str
Path to DEM, needed for calculating visualization functions and saving them.
name : str
Name of BlenderCombination combination.
layers : [BlenderLayer]
List of BlenderLayer instances which will be blended together.
"""
def __init__(self, dem_arr=None, dem_resolution=None, dem_path=None):
self.dem_arr = dem_arr
self.dem_resolution = dem_resolution
self.dem_path = dem_path
self.name = ""
self.layers = []
[docs]
def add_dem_arr(self, dem_arr, dem_resolution):
"""Add or change dem_arr attribute and its resolution dem_resolution attribute."""
self.dem_arr = dem_arr
self.dem_resolution = dem_resolution
[docs]
def add_dem_path(self, dem_path):
"""Add or change dem_path attribute."""
self.dem_path = dem_path
[docs]
def create_layer(self, vis_method=None, normalization="value", minimum=None, maximum=None,
blend_mode="normal", opacity=100, colormap=None, min_colormap_cut=None, max_colormap_cut=None,
image=None, image_path=None):
"""Create BlenderLayer and adds it to layers attribute."""
if vis_method is not None:
layer = BlenderLayer(vis_method=vis_method, normalization=normalization, minimum=minimum, maximum=maximum,
blend_mode=blend_mode, opacity=opacity, colormap=colormap,
min_colormap_cut=min_colormap_cut, max_colormap_cut=max_colormap_cut,
image=image, image_path=image_path)
self.layers.append(layer)
[docs]
def add_layer(self, layer: BlenderLayer):
"""Add BlenderLayer instance to layers attribute."""
if layer.vis is not None:
self.layers.append(layer)
[docs]
def remove_all_layers(self):
"""Empties layers attribute."""
self.layers = []
[docs]
def layers_info(self):
layer_nr = 1
layers_info = []
for layer in self.layers:
layers_info.append("layer {}, visualization = {}, normalization = {}, min = {}, max = {}, blend_mode = {},"
" opacity = {}, colormap = {},"
" min_colormap_cut = {}, max_colormap_cut".format(layer_nr, layer.vis,
layer.normalization,
layer.min,
layer.max, layer.blend_mode,
layer.opacity,
layer.colormap,
layer.min_colormap_cut,
layer.max_colormap_cut))
layer_nr += 1
return layers_info
[docs]
def read_from_file(self, file_path):
"""Reads class attributes from .json file."""
# Example file (for file_path) in dir settings: blender_file_example.txt
dat = open(file_path, "r")
json_data = json.load(dat)
self.read_from_json(json_data)
dat.close()
[docs]
def read_from_json(self, json_data):
"""Fill class attributes with json data."""
self.layers = []
self.name = json_data["combination"]["name"]
layers_data = json_data["combination"]["layers"]
for layer in layers_data:
if layer["visualization_method"] is None:
continue
if layer["visualization_method"].lower() == "none" or layer["visualization_method"].lower() == "null":
continue
else:
vis_method = str(layer["visualization_method"])
norm = str(layer["norm"])
norm_min = float(layer["min"])
norm_max = float(layer["max"])
blend_mode = str(layer["blend_mode"])
opacity = int(layer["opacity"])
colormap = None
min_colormap_cut = None
max_colormap_cut = None
try:
colormap = str(layer["colormap"])
if colormap.lower() == "null" or colormap.lower() == "none":
colormap = None
except:
colormap = None
if colormap is not None:
try:
min_colormap_cut = str(layer["min_colormap_cut"])
if min_colormap_cut.lower() == "null" or min_colormap_cut.lower() == "none":
min_colormap_cut = None
except:
min_colormap_cut = None
try:
max_colormap_cut = str(layer["max_colormap_cut"])
if max_colormap_cut.lower() == "null" or max_colormap_cut.lower() == "none":
max_colormap_cut = None
except:
max_colormap_cut = None
self.add_layer(BlenderLayer(vis_method=vis_method, normalization=norm, minimum=norm_min, maximum=norm_max,
blend_mode=blend_mode, opacity=opacity, colormap=colormap,
min_colormap_cut=min_colormap_cut, max_colormap_cut=max_colormap_cut))
[docs]
def save_to_file(self, file_path):
"""Save layers (manually) to .json file. Parameters image and image_path in each layer have to be None,
visualization has to be correct!"""
json_data = self.to_json()
dat = open(file_path, "w")
dat.write(json.dumps(json_data, indent=4))
dat.close()
[docs]
def to_json(self):
"""Outputs class attributes as json."""
json_data = {"combination": {"name": self.name,
"layers": []
}}
i_layer = 1
for layer in self.layers:
if layer.colormap is None:
json_data["combination"]["layers"].append({"layer": str(i_layer), "visualization_method": layer.vis,
"norm": layer.normalization, "min": layer.min,
"max": layer.max, "blend_mode": layer.blend_mode,
"opacity": layer.opacity})
else:
if layer.min_colormap_cut is None and layer.max_colormap_cut is None:
json_data["combination"]["layers"].append({"layer": str(i_layer), "visualization_method": layer.vis,
"norm": layer.normalization, "min": layer.min,
"max": layer.max, "blend_mode": layer.blend_mode,
"opacity": layer.opacity, "colormap": layer.colormap})
elif layer.min_colormap_cut is not None and layer.max_colormap_cut is None:
json_data["combination"]["layers"].append({"layer": str(i_layer), "visualization_method": layer.vis,
"norm": layer.normalization, "min": layer.min,
"max": layer.max, "blend_mode": layer.blend_mode,
"opacity": layer.opacity, "colormap": layer.colormap,
"min_colormap_cut": layer.min_colormap_cut})
elif layer.min_colormap_cut is None and layer.max_colormap_cut is not None:
json_data["combination"]["layers"].append({"layer": str(i_layer), "visualization_method": layer.vis,
"norm": layer.normalization, "min": layer.min,
"max": layer.max, "blend_mode": layer.blend_mode,
"opacity": layer.opacity, "colormap": layer.colormap,
"max_colormap_cut": layer.max_colormap_cut})
elif layer.min_colormap_cut is not None and layer.max_colormap_cut is not None:
json_data["combination"]["layers"].append({"layer": str(i_layer), "visualization_method": layer.vis,
"norm": layer.normalization, "min": layer.min,
"max": layer.max, "blend_mode": layer.blend_mode,
"opacity": layer.opacity, "colormap": layer.colormap,
"min_colormap_cut": layer.min_colormap_cut,
"max_colormap_cut": layer.max_colormap_cut})
i_layer += 1
return json_data
[docs]
def check_data(self):
for layer in self.layers:
layer.check_data()
[docs]
def render_all_images(self, default=None, save_visualizations=False, save_render_path=None, save_float=True,
save_8bit=False, no_data=None):
"""Render all layers and returns blended image. If specific layer (BlenderLayer) in layers has image
(is not None), method uses this image, if image is None and layer has image_path method reads image from
path. If both image and image_path are None method calculates visualization. If save_visualization is True
method needs dem_path and saves each visualization (if it doesn't exists) in directory of dem_path,
else (save_visualization=False) method needs dem_arr, dem_resolution and calculates each visualization
simultaneously (in memory). Be careful save_visualisation applies only if specific BlenderLayer
image and image_path are None. Parameter no_data changes all pixels with this values to np.nan,
if save_visualizations is Ture it is not needed."""
# Preform checks
self.check_data()
if save_render_path is not None and self.dem_path is None:
raise Exception(
"rvt.blend.BlenderCombination.render_all_images: If you would like to save rendered image (blender), "
"you have to define dem_path (BlenderCombination.add_dem_path())!")
if not save_float and not save_8bit and save_render_path:
raise Exception(
"rvt.blend.BlenderCombination.render_all_images: If you would like to save rendered image (blender), "
"you have to set save_float or save_8bit to True!")
# Prepare directory for saving renders
if save_render_path is not None:
save_render_directory = os.path.abspath(os.path.dirname(save_render_path))
save_render_8bit_path = os.path.join(
save_render_directory,
"{}_8bit.tif".format(os.path.splitext(os.path.basename(save_render_path))[0])
)
else:
save_render_directory = None
save_render_8bit_path = None
# If default (rvt.default.DefaultValues class) is not defined, use predefined values
if default is None:
default = rvt.default.DefaultValues()
# Rendering across all layers - form last to first layer
rendered_image = None
for i_img in range(len(self.layers) - 1, -1, -1):
# Get visualization type (string)
visualization = self.layers[i_img].vis
if visualization is None: # empty layer, skip
continue
# Get other parameters for processing this layer
min_norm = self.layers[i_img].min
max_norm = self.layers[i_img].max
normalization = self.layers[i_img].normalization
image = self.layers[i_img].image
image_path = self.layers[i_img].image_path
if save_visualizations and self.dem_path is None and image_path is None and image is None:
raise Exception(
"rvt.blend.BlenderCombination.render_all_images: If you would like to save visualizations, "
"you have to define dem_path (BlenderCombination.add_dem_path())!")
if not save_visualizations and self.dem_arr is None and self.dem_resolution is None and \
image_path is None and image is None:
raise Exception(
"rvt.blend.BlenderCombination.render_all_images: If you would like to compute visualizations, "
"you have to define dem_arr and its resolution (BlenderCombination.add_dem_arr())!")
# Normalize images
norm_image = None
if image is None and image_path is not None:
# LOAD image from file if it doesn't exist (as an array) but we have image_path present
norm_image = normalize_image(
visualization,
rvt.default.get_raster_arr(image_path)["array"],
min_norm,
max_norm,
normalization
)
elif image is not None:
# if image exist (as an array)
norm_image = normalize_image(
visualization,
image,
min_norm,
max_norm,
normalization
)
else:
# calculate image from DEM
if self.layers[i_img].vis.lower() == "slope gradient":
if save_visualizations:
default.save_slope(dem_path=self.dem_path, custom_dir=save_render_directory, save_float=True,
save_8bit=False)
image_path = default.get_slope_path(self.dem_path)
norm_image = normalize_image(visualization, rvt.default.get_raster_arr(image_path)["array"],
min_norm, max_norm, normalization)
else:
image = default.get_slope(dem_arr=self.dem_arr, resolution_x=self.dem_resolution,
resolution_y=self.dem_resolution, no_data=no_data)
norm_image = normalize_image(visualization, image, min_norm, max_norm, normalization)
elif self.layers[i_img].vis.lower() == "hillshade":
if save_visualizations:
default.save_hillshade(dem_path=self.dem_path, custom_dir=save_render_directory,
save_float=True, save_8bit=False)
image_path = default.get_hillshade_path(self.dem_path)
norm_image = normalize_image(visualization, rvt.default.get_raster_arr(image_path)["array"],
min_norm, max_norm, normalization)
else:
image = default.get_hillshade(dem_arr=self.dem_arr, resolution_x=self.dem_resolution,
resolution_y=self.dem_resolution, no_data=no_data)
norm_image = normalize_image(visualization, image, min_norm, max_norm, normalization)
elif self.layers[i_img].vis.lower() == "shadow":
if save_visualizations:
default.save_hillshade(dem_path=self.dem_path, custom_dir=save_render_directory,
save_float=True, save_8bit=False, save_shadow=True)
image_path = default.get_shadow_path(self.dem_path)
norm_image = normalize_image(visualization, rvt.default.get_raster_arr(image_path)["array"],
min_norm, max_norm, normalization)
else:
image = default.get_shadow(dem_arr=self.dem_arr, resolution=self.dem_resolution,
no_data=no_data)
norm_image = normalize_image(visualization, image, min_norm, max_norm, normalization)
elif self.layers[i_img].vis.lower() == "multiple directions hillshade":
if save_visualizations:
default.save_multi_hillshade(dem_path=self.dem_path, custom_dir=save_render_directory,
save_float=False, save_8bit=True)
image_path = default.get_multi_hillshade_path(self.dem_path, bit8=True)
norm_image = normalize_image("", rvt.default.get_raster_arr(image_path)["array"],
0, 255, normalization)
norm_image = normalize_image(visualization, norm_image,
min_norm, max_norm, normalization)
else:
red_band_arr = rvt.vis.hillshade(dem=self.dem_arr, resolution_x=self.dem_resolution,
resolution_y=self.dem_resolution,
sun_elevation=default.mhs_sun_el, sun_azimuth=315,
no_data=no_data)
green_band_arr = rvt.vis.hillshade(dem=self.dem_arr, resolution_x=self.dem_resolution,
resolution_y=self.dem_resolution,
sun_elevation=default.mhs_sun_el, sun_azimuth=22.5,
no_data=no_data)
blue_band_arr = rvt.vis.hillshade(dem=self.dem_arr, resolution_x=self.dem_resolution,
resolution_y=self.dem_resolution,
sun_elevation=default.mhs_sun_el, sun_azimuth=90,
no_data=no_data)
image = np.array([red_band_arr, green_band_arr, blue_band_arr])
norm_image = normalize_image(visualization, image, min_norm, max_norm, normalization)
elif self.layers[i_img].vis.lower() == "simple local relief model":
if save_visualizations:
default.save_slrm(dem_path=self.dem_path, custom_dir=save_render_directory, save_float=True,
save_8bit=False)
image_path = default.get_slrm_path(self.dem_path)
norm_image = normalize_image(visualization, rvt.default.get_raster_arr(image_path)["array"],
min_norm, max_norm, normalization)
else:
image = default.get_slrm(dem_arr=self.dem_arr, no_data=no_data)
norm_image = normalize_image(visualization, image, min_norm, max_norm, normalization)
elif self.layers[i_img].vis.lower() == "sky-view factor":
if save_visualizations:
default.save_sky_view_factor(dem_path=self.dem_path, save_svf=True, save_asvf=False,
save_opns=False, custom_dir=save_render_directory, save_float=True,
save_8bit=False)
image_path = default.get_svf_path(self.dem_path)
norm_image = normalize_image(visualization, rvt.default.get_raster_arr(image_path)["array"],
min_norm, max_norm, normalization)
else:
image = default.get_sky_view_factor(dem_arr=self.dem_arr, resolution=self.dem_resolution,
compute_svf=True, compute_asvf=False,
compute_opns=False, no_data=no_data)["svf"]
norm_image = normalize_image(visualization, image, min_norm, max_norm, normalization)
elif self.layers[i_img].vis.lower() == "anisotropic sky-view factor":
if save_visualizations:
default.save_sky_view_factor(dem_path=self.dem_path, save_svf=False, save_asvf=True,
save_opns=False, custom_dir=save_render_directory, save_float=True,
save_8bit=False)
image_path = default.get_asvf_path(self.dem_path)
norm_image = normalize_image(visualization, rvt.default.get_raster_arr(image_path)["array"],
min_norm, max_norm, normalization)
else:
image = default.get_sky_view_factor(dem_arr=self.dem_arr, resolution=self.dem_resolution,
compute_svf=False, compute_asvf=True,
compute_opns=False, no_data=no_data)["asvf"]
norm_image = normalize_image(visualization, image, min_norm, max_norm, normalization)
elif self.layers[i_img].vis.lower() == "openness - positive":
if save_visualizations:
default.save_sky_view_factor(dem_path=self.dem_path, save_svf=False, save_asvf=False,
save_opns=True, custom_dir=save_render_directory, save_float=True,
save_8bit=False)
image_path = default.get_opns_path(self.dem_path)
norm_image = normalize_image(visualization, rvt.default.get_raster_arr(image_path)["array"],
min_norm, max_norm, normalization)
else:
image = default.get_sky_view_factor(dem_arr=self.dem_arr, resolution=self.dem_resolution,
compute_svf=False, compute_asvf=False,
compute_opns=True, no_data=no_data)["opns"]
norm_image = normalize_image(visualization, image, min_norm, max_norm, normalization)
elif self.layers[i_img].vis.lower() == "openness - negative":
if save_visualizations:
default.save_neg_opns(dem_path=self.dem_path, custom_dir=save_render_directory, save_float=True,
save_8bit=False)
image_path = default.get_neg_opns_path(self.dem_path)
norm_image = normalize_image(visualization, rvt.default.get_raster_arr(image_path)["array"],
min_norm, max_norm, normalization)
else:
image = default.get_neg_opns(dem_arr=self.dem_arr, resolution=self.dem_resolution,
no_data=no_data)
norm_image = normalize_image(visualization, image, min_norm, max_norm, normalization)
elif self.layers[i_img].vis.lower() == "sky illumination":
if save_visualizations:
default.save_sky_illumination(dem_path=self.dem_path, custom_dir=save_render_directory,
save_float=True, save_8bit=False)
image_path = default.get_sky_illumination_path(self.dem_path)
norm_image = normalize_image(visualization, rvt.default.get_raster_arr(image_path)["array"],
min_norm, max_norm, normalization)
else:
image = default.get_sky_illumination(dem_arr=self.dem_arr, resolution=self.dem_resolution,
no_data=no_data)
norm_image = normalize_image(visualization, image, min_norm, max_norm, normalization)
elif self.layers[i_img].vis.lower() == "local dominance":
if save_visualizations:
default.save_local_dominance(dem_path=self.dem_path, custom_dir=save_render_directory,
save_float=True, save_8bit=False)
image_path = default.get_local_dominance_path(self.dem_path)
norm_image = normalize_image(visualization, rvt.default.get_raster_arr(image_path)["array"],
min_norm, max_norm, normalization)
else:
image = default.get_local_dominance(dem_arr=self.dem_arr, no_data=no_data)
norm_image = normalize_image(visualization, image, min_norm, max_norm, normalization)
elif self.layers[i_img].vis.lower() == "multi-scale relief model":
if save_visualizations:
default.save_msrm(dem_path=self.dem_path, custom_dir=save_render_directory,
save_float=True, save_8bit=False)
image_path = default.get_msrm_path(self.dem_path)
norm_image = normalize_image(visualization, rvt.default.get_raster_arr(image_path)["array"],
min_norm, max_norm, normalization)
else:
image = default.get_msrm(dem_arr=self.dem_arr, resolution=self.dem_resolution, no_data=no_data)
norm_image = normalize_image(visualization, image, min_norm, max_norm, normalization)
elif self.layers[i_img].vis.lower() == "multi-scale topographic position":
if save_visualizations:
default.save_mstp(
dem_path=self.dem_path, custom_dir=save_render_directory, save_float=True, save_8bit=False
)
image_path = default.get_mstp_path(self.dem_path)
norm_image = normalize_image(visualization, rvt.default.get_raster_arr(image_path)["array"],
min_norm, max_norm, normalization)
else:
image = default.get_mstp(dem_arr=self.dem_arr, no_data=no_data)
norm_image = normalize_image(visualization, image, min_norm, max_norm, normalization)
# Apply colormap
colormap = self.layers[i_img].colormap
min_colormap_cut = self.layers[i_img].min_colormap_cut
max_colormap_cut = self.layers[i_img].max_colormap_cut
if colormap is not None and len(image.shape) < 3:
norm_image = gray_scale_to_color_ramp(gray_scale=norm_image, colormap=colormap, output_8bit=False,
min_colormap_cut=min_colormap_cut,
max_colormap_cut=max_colormap_cut)
# Blend current layer with background layer
if rendered_image is None:
# if current layer has visualization applied, but there has been no rendering
# of images yet, than current layer will be the initial value of rendered_image
rendered_image = norm_image
continue
else:
active = norm_image
background = rendered_image
# Scale images if needed
if np.nanmin(active) < 0 or np.nanmax(active) > 1:
active = scale_0_to_1(active)
if np.nanmin(background) < 0 or np.nanmax(background) > 1:
background = scale_0_to_1(background)
# Blend background with active layer
blend_mode = self.layers[i_img].blend_mode
top = blend_images(blend_mode, active, background)
# Apply opacity
opacity = self.layers[i_img].opacity
rendered_image = render_images(top, background, opacity)
if np.nanmin(rendered_image) < 0 or np.nanmax(rendered_image) > 1:
warnings.warn("rvt.blend.BlenderCombination.render_all_images: Rendered image scale distorted")
# Save image to file if path is present
if save_render_path is not None:
if save_float:
rvt.default.save_raster(src_raster_path=self.dem_path, out_raster_path=save_render_path,
out_raster_arr=rendered_image)
if save_8bit:
rendered_image_8bit = rvt.vis.byte_scale(rendered_image, c_min=0, c_max=1)
rvt.default.save_raster(src_raster_path=self.dem_path, out_raster_path=save_render_8bit_path,
out_raster_arr=rendered_image_8bit, e_type=1)
return rendered_image # returns float
[docs]
def create_log_file(self, dem_path, combination_name, render_path, default: rvt.default.DefaultValues,
terrain_sett_name=None, custom_dir=None, computation_time=None):
"""Creates log file in custom_dir, if custom_dir=None it creates it in dem directory (dem_path)."""
dict_arr_res = rvt.default.get_raster_arr(raster_path=dem_path)
resolution = dict_arr_res["resolution"]
arr_shape = np.array(dict_arr_res["array"]).shape
del dict_arr_res
nr_bands = 0
nr_cols = 0
nr_rows = 0
if len(arr_shape) == 3:
nr_bands = arr_shape[0]
nr_rows = arr_shape[1]
nr_cols = arr_shape[2]
elif len(arr_shape) == 2:
nr_bands = 1
nr_rows = arr_shape[0]
nr_cols = arr_shape[1]
dem_dir = os.path.dirname(dem_path)
log_dir = dem_dir
if custom_dir is not None:
log_dir = custom_dir
dem_name = os.path.splitext(os.path.basename(dem_path))[0]
log_file_time = datetime.datetime.now()
log_file_time_str = log_file_time.strftime("%Y-%m-%d_%H-%M-%S")
log_name = "{}_blend_log_{}.txt".format(dem_name, log_file_time_str)
log_path = os.path.join(log_dir, log_name)
dat = open(log_path, "w")
dat.write(
"===============================================================================================\n"
"Relief Visualization Toolbox (python), blender log\n"
"Copyright:\n"
"\tResearch Centre of the Slovenian Academy of Sciences and Arts\n"
"\tUniversity of Ljubljana, Faculty of Civil and Geodetic Engineering\n"
"===============================================================================================\n")
dat.write("\n\n\n")
dat.write("Processing info about visualizations\n"
"===============================================================================================\n\n")
dat.write("# Metadata of the input file\n\n")
dat.write("\tInput filename:\t\t{}\n".format(dem_path))
dat.write("\tNumber of rows:\t\t{}\n".format(nr_rows))
dat.write("\tNumber of columns:\t{}\n".format(nr_cols))
dat.write("\tNumber of bands:\t{}\n".format(nr_bands))
dat.write("\tResolution (x, y):\t{}, {}\n".format(resolution[0], resolution[1]))
dat.write("\n")
dat.write("# Selected visualization parameters\n")
dat.write("\tOverwrite: {}\n".format(default.overwrite))
dat.write("\tVertical exaggeration factor: {}\n".format(default.ve_factor))
dat.write("\n")
dat.write("# Combination:\n\n")
dat.write("Combination name: {}\n".format(combination_name))
if terrain_sett_name is not None:
dat.write("Terrain settings: {}\n".format(terrain_sett_name))
dat.write("\t>> Output render file:\n")
dat.write("\t\t{}\n\n".format(render_path))
dat.write("=== LAYERS ===\n\n")
i_layer = 1
for layer in self.layers:
dat.write("Layer: {}\n".format(i_layer))
dat.write("Visualization: {}\n".format(layer.vis))
if layer.vis.lower() == "hillshade":
dat.write("\ths_sun_el=\t\t{}\n".format(default.hs_sun_el))
dat.write("\ths_sun_azi=\t\t{}\n".format(default.hs_sun_azi))
elif layer.vis.lower() == "shadow":
dat.write("\ths_sun_el=\t\t{}\n".format(default.hs_sun_el))
dat.write("\ths_sun_azi=\t\t{}\n".format(default.hs_sun_azi))
elif layer.vis.lower() == "multiple directions hillshade":
dat.write("\tmhs_sun_el=\t\t{}\n".format(default.mhs_sun_el))
dat.write("\tmhs_nr_dir=\t\t{}\n".format(default.mhs_nr_dir))
elif layer.vis.lower() == "slope gradient":
dat.write("\tslp_output_units=\t\t{}\n".format(default.slp_output_units))
elif layer.vis.lower() == "simple local relief model":
dat.write("\tslrm_rad_cell=\t\t{}\n".format(default.slrm_rad_cell))
elif layer.vis.lower() == "sky-view factor":
dat.write("\tsvf_n_dir=\t\t{}\n".format(default.svf_n_dir))
dat.write("\tsvf_noise=\t\t{}\n".format(default.svf_noise))
dat.write("\tsvf_r_max=\t\t{}\n".format(default.svf_r_max))
elif layer.vis.lower() == "anisotropic sky-view factor":
dat.write("\tsvf_n_dir=\t\t{}\n".format(default.svf_n_dir))
dat.write("\tsvf_noise=\t\t{}\n".format(default.svf_noise))
dat.write("\tsvf_r_max=\t\t{}\n".format(default.svf_r_max))
dat.write("\tasvf_level=\t\t{}\n".format(default.asvf_level))
dat.write("\tasvf_dir=\t\t{}\n".format(default.asvf_dir))
elif layer.vis.lower() == "openness - positive":
dat.write("\tsvf_n_dir=\t\t{}\n".format(default.svf_n_dir))
dat.write("\tsvf_noise=\t\t{}\n".format(default.svf_noise))
dat.write("\tsvf_r_max=\t\t{}\n".format(default.svf_r_max))
elif layer.vis.lower() == "openness - negative":
dat.write("\tsvf_n_dir=\t\t{}\n".format(default.svf_n_dir))
dat.write("\tsvf_noise=\t\t{}\n".format(default.svf_noise))
dat.write("\tsvf_r_max=\t\t{}\n".format(default.svf_r_max))
elif layer.vis.lower() == "sky illumination":
dat.write("\tsim_sky_mod=\t\t{}\n".format(default.sim_sky_mod))
dat.write("\tsim_compute_shadow=\t\t{}\n".format(default.sim_compute_shadow))
dat.write("\tsim_shadow_az=\t\t{}\n".format(default.sim_shadow_az))
dat.write("\tsim_shadow_el=\t\t{}\n".format(default.sim_shadow_el))
dat.write("\tsim_nr_dir=\t\t{}\n".format(default.sim_nr_dir))
dat.write("\tsim_shadow_dist=\t\t{}\n".format(default.sim_shadow_dist))
elif layer.vis.lower() == "local dominance":
dat.write("\t\tld_rad_inc=\t\t{}\n".format(default.ld_rad_inc))
dat.write("\t\tld_min_rad=\t\t{}\n".format(default.ld_min_rad))
dat.write("\t\tld_max_rad=\t\t{}\n".format(default.ld_max_rad))
dat.write("\t\tld_anglr_res=\t\t{}\n".format(default.ld_anglr_res))
dat.write("\t\tld_observer_h=\t\t{}\n".format(default.ld_observer_h))
elif layer.vis.lower() == "multi-scale relief model":
dat.write("\t\tmsrm_feature_min=\t\t{}\n".format(default.msrm_feature_min))
dat.write("\t\tmsrm_feature_max=\t\t{}\n".format(default.msrm_feature_max))
dat.write("\t\tmsrm_scaling_factor=\t\t{}\n".format(default.msrm_scaling_factor))
elif layer.vis.lower() == "multi-scale topographic position":
dat.write("\t\tmstp_local_scale=\t\t({}, {}, {})\n".format(
default.mstp_local_scale[0], default.mstp_local_scale[1], default.mstp_local_scale[2]))
dat.write("\t\tmstp_meso_scale=\t\t({}, {}, {})\n".format(
default.mstp_meso_scale[0], default.mstp_meso_scale[1], default.mstp_meso_scale[2]))
dat.write("\t\tmstp_broad_scale=\t\t({}, {}, {})\n".format(
default.mstp_broad_scale[0], default.mstp_broad_scale[1], default.mstp_broad_scale[2]))
dat.write("\t\tmstp_lightness=\t\t{}\n".format(default.mstp_lightness))
dat.write("Norm: {}\n".format(layer.normalization))
dat.write("Linear normalization, min: {}, max: {}\n".format(layer.min, layer.max))
dat.write("Opacity: {}\n".format(layer.opacity))
if layer.colormap is not None:
dat.write("Colormap: {}\n".format(layer.colormap))
if layer.min_colormap_cut is not None:
dat.write("Minimum Colormap cut: {}\n".format(layer.min_colormap_cut))
if layer.max_colormap_cut is not None:
dat.write("Maximum Colormap cut: {}\n".format(layer.max_colormap_cut))
dat.write("\n")
i_layer += 1
if computation_time is not None:
dat.write("# Computation time: {}".format(computation_time))
dat.close()
[docs]
def compare_2_combinations(combination1: BlenderCombination, combination2: BlenderCombination):
if len(combination1.layers) != len(combination2.layers):
return False
for i_layer in range(len(combination1.layers)):
if combination1.layers[i_layer].vis.lower() != combination2.layers[i_layer].vis.lower():
return False
# if combination1.layers[i_layer].normalization.lower() != combination2.layers[i_layer].normalization.lower():
# return False
# if combination1.layers[i_layer].min != combination2.layers[i_layer].min:
# return False
# if combination1.layers[i_layer].max != combination2.layers[i_layer].max:
# return False
if combination1.layers[i_layer].blend_mode.lower() != combination2.layers[i_layer].blend_mode.lower():
return False
if combination1.layers[i_layer].opacity != combination2.layers[i_layer].opacity:
return False
return True
[docs]
class BlenderCombinations:
"""
Class for storing combinations.
Attributes
----------
combinations : [BlenderCombination]
List of BlenderCombination instances.
"""
def __init__(self):
self.combinations = [] # list of BlenderCombination
[docs]
def add_combination(self, combination: BlenderCombination, name=None):
"""Adds combination if parameter name not None it renames combination."""
if name is not None:
combination.name = name
self.combinations.append(combination)
[docs]
def remove_all_combinations(self):
"""Removes all combinations from self.combinations."""
self.combinations = []
[docs]
def select_combination_by_name(self, name):
"""Select first combination where self.combinations.BlenderCombination.name = name."""
for combination in self.combinations:
if combination.name == name:
return combination
[docs]
def remove_combination_by_name(self, name):
"""Removes all combinations where self.combinations.BlenderCombination.name = name.
If combinations list is empty function returns 0, else 1."""
new_combinations = []
for combination in self.combinations:
if combination.name != name:
new_combinations.append(combination)
self.combinations = new_combinations
if not new_combinations:
return 0
else:
return 1
[docs]
def read_from_file(self, file_path):
"""Reads combinations from .json file."""
self.combinations = []
dat = open(file_path, "r")
json_data = json.load(dat)
combinations_data = json_data["combinations"]
for combination_data in combinations_data:
combination = BlenderCombination()
combination.read_from_json(combination_data)
self.combinations.append(combination)
dat.close()
[docs]
def save_to_file(self, file_path):
"""Saves combination to .json file."""
json_data = {"combinations": []}
for combination in self.combinations:
json_data["combinations"].append(combination.to_json())
dat = open(file_path, "w")
dat.write(json.dumps(json_data, indent=4))
dat.close()
[docs]
def combination_in_combinations(self, input_combination: BlenderCombination):
"""If input_combination (BlenderCombination) has same attributes as one of the combinations (self), method
returns name of the combination (from combinations). If there is no equal one it returns None."""
for combination in self.combinations:
if compare_2_combinations(input_combination, combination):
return combination.name
return None
[docs]
def combinations_names(self):
"""Returns list of combinations names."""
names_list = []
for combination in self.combinations:
names_list.append(combination.name)
return names_list
[docs]
class TerrainSettings:
"""Terrain settings for GUI."""
def __init__(self):
self.name = None
# slope gradient
self.slp_output_units = None
# hillshade
self.hs_sun_azi = None
self.hs_sun_el = None
# multi hillshade
self.mhs_nr_dir = None
self.mhs_sun_el = None
# simple local relief model
self.slrm_rad_cell = None
# sky view factor
self.svf_n_dir = None
self.svf_r_max = None
self.svf_noise = None
# anisotropic sky-view factor
self.asvf_dir = None
self.asvf_level = None
# positive openness
# negative openness
# sky_illumination
self.sim_sky_mod = None
self.sim_compute_shadow = None
self.sim_nr_dir = None
self.sim_shadow_dist = None
self.sim_shadow_az = None
self.sim_shadow_el = None
# local dominance
self.ld_min_rad = None
self.ld_max_rad = None
self.ld_rad_inc = None
self.ld_anglr_res = None
self.ld_observer_h = None
# multi-scale relief model
self.msrm_feature_min = None
self.msrm_feature_max = None
self.msrm_scaling_factor = None
# multi-scale topographic position
self.mstp_local_scale = None
self.mstp_meso_scale = None
self.mstp_broad_scale = None
self.mstp_lightness = None
# linear histogram stretches tuple(min, max)
self.hs_stretch = None
self.mhs_stretch = None
self.slp_stretch = None
self.slrm_stretch = None
self.svf_stretch = None
self.asvf_stretch = None
self.pos_opns_stretch = None
self.neg_opns_stretch = None
self.sim_stretch = None
self.ld_stretch = None
self.msrm_stretch = None
self.mstp_stretch = None
[docs]
def read_from_file(self, file_path):
"""Reads combinations from .json file."""
dat = open(file_path, "r")
json_data = json.load(dat)
self.read_from_json(json_data)
dat.close()
[docs]
def read_from_json(self, json_data):
"""Reads json dict and fills self attributes."""
self.__init__()
terrain_data = json_data["terrain_settings"]
self.name = terrain_data["name"]
try:
self.slp_output_units = str(terrain_data["Slope gradient"]["slp_output_units"]["value"])
except KeyError:
pass
try:
self.hs_sun_azi = int(terrain_data["Hillshade"]["hs_sun_azi"]["value"])
except KeyError:
pass
try:
self.hs_sun_el = int(terrain_data["Hillshade"]["hs_sun_el"]["value"])
except KeyError:
pass
try:
self.mhs_nr_dir = int(terrain_data["Multiple directions hillshade"]["mhs_nr_dir"]["value"])
except KeyError:
pass
try:
self.mhs_sun_el = int(terrain_data["Multiple directions hillshade"]["mhs_sun_el"]["value"])
except KeyError:
pass
try:
self.slrm_rad_cell = int(terrain_data["Simple local relief model"]["slrm_rad_cell"]["value"])
except KeyError:
pass
try:
self.svf_n_dir = int(terrain_data["Sky-View Factor"]["svf_n_dir"]["value"])
except KeyError:
pass
try:
self.svf_r_max = int(terrain_data["Sky-View Factor"]["svf_r_max"]["value"])
except KeyError:
pass
try:
self.svf_noise = int(terrain_data["Sky-View Factor"]["svf_noise"]["value"])
except KeyError:
pass
try:
self.asvf_dir = int(terrain_data["Anisotropic Sky-View Factor"]["asvf_dir"]["value"])
except KeyError:
pass
try:
self.asvf_level = int(terrain_data["Anisotropic Sky-View Factor"]["asvf_level"]["value"])
except KeyError:
pass
try:
self.sim_sky_mod = str(terrain_data["Sky illumination"]["sim_sky_mod"]["value"])
except KeyError:
pass
try:
self.sim_compute_shadow = int(terrain_data["Sky illumination"]["sim_compute_shadow"]["value"])
except KeyError:
pass
try:
self.sim_nr_dir = int(terrain_data["Sky illumination"]["sim_nr_dir"]["value"])
except KeyError:
pass
try:
self.sim_shadow_dist = int(terrain_data["Sky illumination"]["sim_shadow_dist"]["value"])
except KeyError:
pass
try:
self.sim_shadow_az = int(terrain_data["Sky illumination"]["sim_shadow_az"]["value"])
except KeyError:
pass
try:
self.sim_shadow_el = int(terrain_data["Sky illumination"]["sim_shadow_el"]["value"])
except KeyError:
pass
try:
self.ld_min_rad = int(terrain_data["Local dominance"]["ld_min_rad"]["value"])
except KeyError:
pass
try:
self.ld_max_rad = int(terrain_data["Local dominance"]["ld_max_rad"]["value"])
except KeyError:
pass
try:
self.ld_rad_inc = int(terrain_data["Local dominance"]["ld_rad_inc"]["value"])
except KeyError:
pass
try:
self.ld_anglr_res = int(terrain_data["Local dominance"]["ld_anglr_res"]["value"])
except KeyError:
pass
try:
self.ld_observer_h = float(terrain_data["Local dominance"]["ld_observer_h"]["value"])
except KeyError:
pass
try:
self.msrm_feature_min = float(terrain_data["Multi-scale relief model"]["msrm_feature_min"]["value"])
except KeyError:
pass
try:
self.msrm_feature_max = float(terrain_data["Multi-scale relief model"]["msrm_feature_max"]["value"])
except KeyError:
pass
try:
self.msrm_scaling_factor = int(terrain_data["Multi-scale relief model"]["msrm_scaling_factor"]["value"])
except KeyError:
pass
try:
self.mstp_local_scale = (int(terrain_data["Multi-scale topographic position"]["mstp_local_scale"]["min"]),
int(terrain_data["Multi-scale topographic position"]["mstp_local_scale"]["max"]),
int(terrain_data["Multi-scale topographic position"]["mstp_local_scale"]["step"]))
except KeyError:
pass
try:
self.mstp_meso_scale = (int(terrain_data["Multi-scale topographic position"]["mstp_meso_scale"]["min"]),
int(terrain_data["Multi-scale topographic position"]["mstp_meso_scale"]["max"]),
int(terrain_data["Multi-scale topographic position"]["mstp_meso_scale"]["step"]))
except KeyError:
pass
try:
self.mstp_broad_scale = (int(terrain_data["Multi-scale topographic position"]["mstp_broad_scale"]["min"]),
int(terrain_data["Multi-scale topographic position"]["mstp_broad_scale"]["max"]),
int(terrain_data["Multi-scale topographic position"]["mstp_broad_scale"]["step"]))
except KeyError:
pass
try:
self.mstp_lightness = float(terrain_data["Multi-scale topographic position"]["mstp_lightness"]["value"])
except KeyError:
pass
try:
self.slp_stretch = (float(terrain_data["Slope gradient"]["stretch"]["min"]),
float(terrain_data["Slope gradient"]["stretch"]["max"]))
except KeyError:
pass
try:
self.hs_stretch = (float(terrain_data["Hillshade"]["stretch"]["min"]),
float(terrain_data["Hillshade"]["stretch"]["max"]))
except KeyError:
pass
try:
self.mhs_stretch = (float(terrain_data["Multiple directions hillshade"]["stretch"]["min"]),
float(terrain_data["Multiple directions hillshade"]["stretch"]["max"]))
except KeyError:
pass
try:
self.slrm_stretch = (float(terrain_data["Simple local relief model"]["stretch"]["min"]),
float(terrain_data["Simple local relief model"]["stretch"]["max"]))
except KeyError:
pass
try:
self.svf_stretch = (float(terrain_data["Sky-View Factor"]["stretch"]["min"]),
float(terrain_data["Sky-View Factor"]["stretch"]["max"]))
except KeyError:
pass
try:
self.asvf_stretch = (float(terrain_data["Anisotropic Sky-View Factor"]["stretch"]["min"]),
float(terrain_data["Anisotropic Sky-View Factor"]["stretch"]["max"]))
except KeyError:
pass
try:
self.pos_opns_stretch = (float(terrain_data["Openness - Positive"]["stretch"]["min"]),
float(terrain_data["Openness - Positive"]["stretch"]["max"]))
except KeyError:
pass
try:
self.neg_opns_stretch = (float(terrain_data["Openness - Negative"]["stretch"]["min"]),
float(terrain_data["Openness - Negative"]["stretch"]["max"]))
except KeyError:
pass
try:
self.neg_opns_stretch = (float(terrain_data["Sky illumination"]["stretch"]["min"]),
float(terrain_data["Sky illumination"]["stretch"]["max"]))
except KeyError:
pass
try:
self.neg_opns_stretch = (float(terrain_data["Local dominance"]["stretch"]["min"]),
float(terrain_data["Local dominance"]["stretch"]["max"]))
except KeyError:
pass
try:
self.msrm_stretch = (float(terrain_data["Multi-scale relief model"]["stretch"]["min"]),
float(terrain_data["Multi-scale relief model"]["stretch"]["max"]))
except KeyError:
pass
try:
self.mstp_stretch = (float(terrain_data["Multi-scale topographic position"]["stretch"]["min"]),
float(terrain_data["Multi-scale topographic position"]["stretch"]["max"]))
except KeyError:
pass
[docs]
def apply_terrain(self, default: rvt.default.DefaultValues, combination: BlenderCombination):
"""It overwrites default (DefaultValues) and combination (BlenderCombination),
with self values that are not None."""
if self.slp_output_units is not None:
default.slp_output_units = self.slp_output_units
if self.hs_sun_azi is not None:
default.hs_sun_azi = self.hs_sun_azi
if self.hs_sun_el is not None:
default.hs_sun_el = self.hs_sun_el
if self.mhs_nr_dir is not None:
default.mhs_nr_dir = self.mhs_nr_dir
if self.mhs_sun_el is not None:
default.mhs_sun_el = self.mhs_sun_el
if self.slrm_rad_cell is not None:
default.slrm_rad_cell = self.slrm_rad_cell
if self.svf_n_dir is not None:
default.svf_n_dir = self.svf_n_dir
if self.svf_r_max is not None:
default.svf_r_max = self.svf_r_max
if self.svf_noise is not None:
default.svf_noise = self.svf_noise
if self.asvf_dir is not None:
default.asvf_dir = self.asvf_dir
if self.asvf_level is not None:
default.asvf_level = self.asvf_level
if self.sim_sky_mod is not None:
default.sim_sky_mod = self.sim_sky_mod
if self.sim_compute_shadow is not None:
default.sim_compute_shadow = self.sim_compute_shadow
if self.sim_nr_dir is not None:
default.sim_nr_dir = self.sim_nr_dir
if self.sim_shadow_dist is not None:
default.sim_shadow_dist = self.sim_shadow_dist
if self.sim_shadow_az is not None:
default.sim_shadow_az = self.sim_shadow_az
if self.sim_shadow_el is not None:
default.sim_shadow_el = self.sim_shadow_el
if self.ld_min_rad is not None:
default.ld_min_rad = self.ld_min_rad
if self.ld_max_rad is not None:
default.ld_max_rad = self.ld_max_rad
if self.ld_rad_inc is not None:
default.ld_rad_inc = self.ld_rad_inc
if self.ld_anglr_res is not None:
default.ld_anglr_res = self.ld_anglr_res
if self.ld_observer_h is not None:
default.ld_observer_h = self.ld_observer_h
if self.msrm_feature_min is not None:
default.msrm_feature_min = self.msrm_feature_min
if self.msrm_feature_max is not None:
default.msrm_feature_max = self.msrm_feature_max
if self.msrm_scaling_factor is not None:
default.msrm_scaling_factor = self.msrm_scaling_factor
if self.mstp_local_scale is not None:
default.mstp_local_scale = self.mstp_local_scale
if self.mstp_meso_scale is not None:
default.mstp_meso_scale = self.mstp_meso_scale
if self.mstp_broad_scale is not None:
default.mstp_broad_scale = self.mstp_broad_scale
if self.mstp_lightness is not None:
default.mstp_lightness = self.mstp_lightness
# linear histogram stretches, combination values overwrite
for layer in combination.layers:
if layer.vis.lower() == "slope gradient" and self.slp_stretch is not None:
layer.min = self.slp_stretch[0]
layer.max = self.slp_stretch[1]
elif layer.vis.lower() == "hillshade" and self.hs_stretch is not None:
layer.min = self.hs_stretch[0]
layer.max = self.hs_stretch[1]
elif layer.vis.lower() == "multiple directions hillshade" and self.mhs_stretch is not None:
layer.min = self.mhs_stretch[0]
layer.max = self.mhs_stretch[1]
elif layer.vis.lower() == "simple local relief model" and self.slrm_stretch is not None:
layer.min = self.slrm_stretch[0]
layer.max = self.slrm_stretch[1]
elif layer.vis.lower() == "sky-view factor" and self.svf_stretch is not None:
layer.min = self.svf_stretch[0]
layer.max = self.svf_stretch[1]
elif layer.vis.lower() == "anisotropic sky-view factor" and self.asvf_stretch is not None:
layer.min = self.asvf_stretch[0]
layer.max = self.asvf_stretch[1]
elif layer.vis.lower() == "openness - positive" and self.pos_opns_stretch is not None:
layer.min = self.pos_opns_stretch[0]
layer.max = self.pos_opns_stretch[1]
elif layer.vis.lower() == "openness - negative" and self.neg_opns_stretch is not None:
layer.min = self.neg_opns_stretch[0]
layer.max = self.neg_opns_stretch[1]
elif layer.vis.lower() == "sky illumination" and self.sim_stretch is not None:
layer.min = self.sim_stretch[0]
layer.max = self.sim_stretch[1]
elif layer.vis.lower() == "local dominance" and self.ld_stretch is not None:
layer.min = self.ld_stretch[0]
layer.max = self.ld_stretch[1]
elif layer.vis.lower() == "multi-scale relief model" and self.msrm_stretch is not None:
layer.min = self.msrm_stretch[0]
layer.max = self.msrm_stretch[1]
elif layer.vis.lower() == "multi-scale topographic position" and self.mstp_stretch is not None:
layer.min = self.mstp_stretch[0]
layer.max = self.mstp_stretch[1]
[docs]
class TerrainsSettings:
"""Multiple Terrain settings."""
def __init__(self):
self.terrains_settings = []
[docs]
def read_from_file(self, file_path):
"""Reads combinations from .json file."""
dat = open(file_path, "r")
json_data = json.load(dat)
terrains_settings_json = json_data["terrains_settings"]
for terrain_json in terrains_settings_json:
terrain_settings = TerrainSettings()
terrain_settings.read_from_json(terrain_json)
self.terrains_settings.append(terrain_settings)
dat.close()
[docs]
def select_terrain_settings_by_name(self, name):
"""Select first combination where self.combinations.BlenderCombination.name = name."""
for terrain_setting in self.terrains_settings:
if terrain_setting.name == name:
return terrain_setting
# Advance blending combinations
[docs]
def color_relief_image_map(dem, resolution, default: rvt.default.DefaultValues = rvt.default.DefaultValues(),
colormap="OrRd", min_colormap_cut=0, max_colormap_cut=1, no_data=None):
"""
RVT Color relief image map (CRIM)
Blending combination where layers are:
1st: Openness positive - Openness negative, overlay, 50% opacity
2nd: Openness positive - Openness negative, luminosity, 50% opacity
3rd: Slope gradient, colored with matplotlib colormap
Parameters
----------
dem : numpy.ndarray
Input digital elevation model as 2D numpy array.
resolution : float
DEM pixel size.
default : rvt.default.DefaultValues
Default values for visualization functions.
colormap : str
Colormap form matplotlib (https://matplotlib.org/3.3.2/tutorials/colors/colormaps.html).
min_colormap_cut : float
What lower part of colormap to cut. Between 0 and 1, if 0.2 it cuts off (deletes) 20% of lower colors in
colormap.
If None cut is not applied.
max_colormap_cut : float
What upper part of colormap to cut. Between 0 and 1, if 0.8 it cuts off (deletes) 20% of upper colors in
colormap.
If None cut is not applied.
no_data : int or float
Value that represents no_data, all pixels with this value are changed to np.nan .
Returns
-------
crim_out : numpy.ndarray
2D numpy result array of Color relief image map.
"""
slope_norm = ("value", 0, 0.8) # ("value", 0, 50) deg
op_on_norm = ("value", -28, 28)
default.svf_r_max = 10
if no_data is not None:
dem[dem == no_data] = np.nan
opns_pos_arr = default.get_sky_view_factor(dem_arr=dem, resolution=resolution,
compute_svf=False, compute_asvf=False, compute_opns=True,
no_data=None)["opns"]
opns_neg_arr = default.get_sky_view_factor(dem_arr=-1 * dem, resolution=resolution,
compute_svf=False, compute_asvf=False, compute_opns=True,
no_data=None)["opns"]
opns_pos_neg_arr = opns_pos_arr - opns_neg_arr
slope_arr = rvt.vis.slope_aspect(
dem=dem,
resolution_x=resolution,
resolution_y=resolution,
output_units="radian"
)["slope"]
blend_combination = rvt.blend.BlenderCombination()
blend_combination.create_layer(vis_method="Openness_Pos-Neg", normalization=op_on_norm[0], minimum=op_on_norm[1],
maximum=op_on_norm[2], blend_mode="overlay", opacity=50,
image=opns_pos_neg_arr)
blend_combination.create_layer(vis_method="Openness_Pos-Neg", normalization=op_on_norm[0], minimum=op_on_norm[1],
maximum=op_on_norm[2], blend_mode="luminosity", opacity=50,
image=opns_pos_neg_arr)
blend_combination.create_layer(vis_method="slope gradient rad", normalization=slope_norm[0], minimum=slope_norm[1],
maximum=slope_norm[2], blend_mode="normal", opacity=100, colormap=colormap,
min_colormap_cut=min_colormap_cut, max_colormap_cut=max_colormap_cut,
image=slope_arr)
crim_out = blend_combination.render_all_images()
return crim_out
[docs]
def e3mstp(dem, resolution, default: rvt.default.DefaultValues = rvt.default.DefaultValues(), no_data=None):
"""
RVT enhanced version 3 Multi-scale topographic position (e3MSTP)
Blending combination where layers are:
1st: Simple local relief model (SLRM), screen, 25% opacity
2nd: Color relief image map where cmap=Reds_r(0.5-1) (CRIM_Reds_r), soft_light, 70% opacity
3rd: Multi-scale topographic position (MSTP)
Parameters
----------
dem : numpy.ndarray
Input digital elevation model as 2D numpy array.
resolution : float
DEM pixel size.
default : rvt.default.DefaultValues
Default values for visualization functions.
no_data : int or float
Value that represents no_data, all pixels with this value are changed to np.nan .
Returns
-------
crim_out : numpy.ndarray
2D numpy result array of Color relief image map.
"""
if no_data is not None:
dem[dem == no_data] = np.nan
slrm_arr = default.get_slrm(dem_arr=dem)
crim_red_arr = color_relief_image_map(dem=dem, resolution=resolution, default=default,
colormap="OrRd", min_colormap_cut=0, max_colormap_cut=1)
mstp_arr = default.get_mstp(dem_arr=dem)
blend_combination = rvt.blend.BlenderCombination()
blend_combination.create_layer(vis_method="slrm", normalization="value",
minimum=-0.5,
maximum=0.5, blend_mode="screen", opacity=25,
image=slrm_arr)
blend_combination.create_layer(vis_method="crim_red", normalization="value",
minimum=0,
maximum=1, blend_mode="soft_light", opacity=70,
image=crim_red_arr)
blend_combination.create_layer(vis_method="mstp",
normalization="value", minimum=0, maximum=1,
blend_mode="normal", opacity=100,
image=mstp_arr)
e3mstp_out = blend_combination.render_all_images()
return e3mstp_out