# This file is part of Honeybee. # # Copyright (c) 2022, Ladybug Tools. # You should have received a copy of the GNU Affero General Public License # along with Honeybee; If not, see . # # @license AGPL-3.0-or-later """ Convert a High Dynamic Range (HDR) image file into a falsecolor version of itself. - Args: _hdr: Path to a High Dynamic Range (HDR) image file. max_: A number to set the upper boundary of the legend. The default is dictated based on the legend_unit_. seg_count_: An interger representing the number of steps between the high and low boundary of the legend. The default is set to 10 and any custom values input in here should always be greater than or equal to 2. legend_height_: An integer for the height of the legend in pixels. Set to 0 to completely remove the legend from the output. (Default: 200). legend_width_: An integer for the width of the legend in pixels. Set to 0 to completely remove the legend from the output. (Default: 100). legend_unit_: Text for the unit of the legend. If unspecified, an attempt will be made to sense the metric from the input image file. Typical examples include lux, W/m2, cd/m2, w/sr-m2. conversion_: Number for the conversion factor (aka. multiplier) for the results. The default is either 1 or 179 depending on whether the image is for radiance or irradiance to luminance or illuminance, respectively. contour_lines_: Set to True ro render the image with colored contour lines. extrema_: Set to True to cause extrema points to be printed on the brightest and darkest pixels of the input picture. mask_: A boolen to note whether pixels with a value of zero should be masked in black. (Default: False). color_palette_: Optional interger or text to change the color palette. Choose from the following. * 0 = def - default colors * 1 = pm3d - a variation of the default colors * 2 = spec - the old spectral mapping * 3 = hot - a thermal scale Returns: hdr: Path to the resulting falsecolor HDR file. This can be plugged into the Ladybug "Image Viewer" component to preview the image. It can also be plugged into the "HB HDR to GIF" component to get a GIF image that is more portable and easily previewed by different software. """ ghenv.Component.Name = 'HB False Color' ghenv.Component.NickName = 'FalseColor' ghenv.Component.Message = '1.5.0' ghenv.Component.Category = 'HB-Radiance' ghenv.Component.SubCategory = '4 :: Results' ghenv.Component.AdditionalHelpFromDocStrings = '3' import os import subprocess import re try: # import honeybee_radiance_command dependencies from honeybee_radiance_command.falsecolor import Falsecolor from honeybee_radiance_command.pcomb import Pcomb except ImportError as e: raise ImportError('\nFailed to import honeybee_radiance_command:\n\t{}'.format(e)) try: # import honeybee_radiance dependencies from honeybee_radiance.config import folders as rad_folders except ImportError as e: raise ImportError('\nFailed to import honeybee_radiance:\n\t{}'.format(e)) try: # import ladybug_rhino dependencies from ladybug_rhino.grasshopper import all_required_inputs except ImportError as e: raise ImportError('\nFailed to import ladybug_rhino:\n\t{}'.format(e)) # check the Radiance date of the installed radiance try: # import lbt_recipes dependencies from lbt_recipes.version import check_radiance_date except ImportError as e: raise ImportError('\nFailed to import lbt_recipes:\n\t{}'.format(e)) check_radiance_date() def sense_metric_from_hdr(hdr_path): """Sense the metric/units of a given HDR file from its properties. Args: hdr_path: The path to an HDR image file Returns: Text for the units of the file (either 'lux', 'W/m2', 'cd/m2', 'W/sr-m2') """ with open(hdr_path, 'r') as hdr_file: for lineCount, line in enumerate(hdr_file): if lineCount < 10: low_line = line.strip().lower() if low_line.startswith('oconv') and low_line.endswith('.sky'): return 'W/sr-m2' # this is an image of a sky if low_line.startswith('rpict'): if line.find('_irradiance.vf') > -1: return 'W/m2' if line.find('_radiance.vf') > -1: return 'W/sr-m2' if line.find('-i') > -1 and not line.find('-i-') > -1: return 'lux' else: # we have passed the header of the file return 'cd/m2' # luminance def is_fisheye(hdr_path): """Sense whether a given HDR file is a fisheye. Args: hdr_path: The path to an HDR image file Returns: Text for the units of the file (either 'lux', 'W/m2', 'cd/m2', 'W/sr-m2') """ with open(hdr_path, 'r') as hdr_file: for lineCount, line in enumerate(hdr_file): if lineCount < 10: if '-vth' in line: return True else: return False def get_dimensions(img_dim): """Get integers for the dimensions of an image from the pcomb stdout. Args: img_dim: Text string that is returned from the pcomb function Returns: Two integers for the dimensions of the HDR image """ dimensions = [] for d in ['+X', '-Y']: regex = r'\%s\s+(\d+)' % d matches = re.finditer(regex, img_dim, re.MULTILINE) dim = next(matches).groups()[0] dimensions.append(int(dim)) return dimensions if all_required_inputs(ghenv.Component): # set up the paths for the various files used in translation img_dir = os.path.dirname(_hdr) input_image = os.path.basename(_hdr) new_image = input_image.lower().replace('.hdr', '_falsecolor.HDR') hdr = os.path.join(img_dir, new_image) # set default properties seg_count_ = seg_count_ if seg_count_ is not None else 10 if legend_unit_ is None: legend_unit_ = sense_metric_from_hdr(_hdr) if conversion_ is None: if legend_unit_ in ('W/sr-m2', 'W/m2'): conversion_ = 1 else: conversion_ = 179 if max_ is None: # get the max value by running pextrem pextrem_exe = os.path.join(rad_folders.radbin_path, 'pextrem.exe') if \ os.name == 'nt' else os.path.join(rad_folders.radbin_path, 'pextrem') use_shell = True if os.name == 'nt' else False cmds = [pextrem_exe, '-o', _hdr] process = subprocess.Popen(cmds, stdout=subprocess.PIPE, shell=use_shell) stdout = process.communicate() max_rgb = stdout[0].split('\n')[1] max_ = (sum([float(x) for x in max_rgb.split(' ')[2:]]) / 3) * conversion_ if legend_unit_ == 'W/sr-m2' and max_ > 200: # sun pixel overpowering image max_ = max_ / 50000 max_ = str(round(max_, 1)) if max_ >= 0.1 else str(max_) # create the command to run falsecolor mask = True if mask_ and is_fisheye(_hdr) else False out_img = new_image if not mask else input_image.lower().replace('.hdr', '_fc_temp.HDR') falsecolor = Falsecolor(input=input_image, output=out_img) falsecolor.options.s = max_ falsecolor.options.n = seg_count_ falsecolor.options.l = legend_unit_ falsecolor.options.m = conversion_ if contour_lines_: falsecolor.options.cl = True falsecolor.options.p = input_image if extrema_: falsecolor.options.e = True if legend_height_ is not None: falsecolor.options.lh = legend_height_ if legend_width_ is not None: falsecolor.options.lw = legend_width_ if color_palette_: PALETTE_DICT = { '0': 'def', '1': 'pm3d', '2': 'spec', '3': 'hot', 'def': 'def', 'pm3d': 'pm3d', 'spec': 'spec', 'hot': 'hot' } falsecolor.options.pal = PALETTE_DICT[color_palette_] # run the falsecolor command env = None if rad_folders.env != {}: env = rad_folders.env env = dict(os.environ, **env) if env else None falsecolor.run(env, cwd=img_dir) # if we should maske, then run an additional pcomb command if mask: # get the dimensions of the image getinfo_exe = os.path.join(rad_folders.radbin_path, 'getinfo.exe') if \ os.name == 'nt' else os.path.join(rad_folders.radbin_path, 'getinfo') cmds = [getinfo_exe, '-d', _hdr] use_shell = True if os.name == 'nt' else False process = subprocess.Popen(cmds, stdout=subprocess.PIPE, shell=use_shell) stdout = process.communicate() img_dim = stdout[0] x, y = get_dimensions(img_dim) # mask the image xw = legend_width_ if legend_width_ is not None else 100 lh = int(legend_height_ * 1.17) if legend_height_ is not None else (200 * 1.17) yw = lh - y if lh - y > 0 else 0 expression = 's(x):x*x;' \ 'm=if((xmax-{0})*(ymax-{1})/4-s(x-{0}-(xmax-{0})/2)-s(y-(ymax-{1})/2),1,if({0}-x,1,0));' \ 'ro=m*ri(1);' \ 'go=m*gi(1);' \ 'bo=m*bi(1)'.format(xw, yw) pcomb = Pcomb(input=out_img, output=new_image) pcomb.options.e = expression pcomb.run(env, cwd=img_dir)