Source code for honeybee_radiance_postprocess.cli.grid

"""honeybee radiance postprocess grid commands."""
import click
import sys
import logging
import json
import numpy as np
from pathlib import Path

from honeybee_radiance_postprocess.reader import binary_to_array
from ..annualdaylight import _annual_daylight_vis_metadata

_logger = logging.getLogger(__name__)


@click.group(help='Commands for generating and modifying sensor grids.')
def grid():
    pass


@grid.command('merge-folder')
@click.argument(
    'input-folder',
    type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True))
@click.argument(
    'output-folder',
    type=click.Path(file_okay=False, dir_okay=True, resolve_path=True))
@click.argument('extension', type=str)
@click.option(
    '--dist-info', '-di',
    help='An optional input for distribution information to put the grids back together '
    '. Alternatively, the command will look for a _redist_info.json file inside the '
    'folder.', type=click.Path(file_okay=True, dir_okay=False, resolve_path=True)
)
@click.option(
    '--output-extension', '-oe',
    help='Output file extension. This is only used if as_text is set to True. '
    'Otherwise the output extension will be npy.', default='ill', type=click.STRING
)
@click.option(
    '--as-text', '-at',
    help='Set to True if the output files should be saved as text instead of '
    'NumPy files.', default=False, type=click.BOOL
)
@click.option(
    '--fmt',
    help='Format for the output files when saved as text.', default='%.2f',
    type=click.STRING
)
@click.option(
    '--delimiter',
    help='Delimiter for the output files when saved as text.',
    type=click.Choice(['space', 'tab']), default='tab'
)
def merge_grid_folder(input_folder, output_folder, extension, dist_info,
                      output_extension, as_text, fmt, delimiter):
    """Restructure files in a distributed folder.

    \b
    Args:
        input_folder: Path to input folder.
        output_folder: Path to the new restructured folder
        extension: Extension of the files to collect data from. It will be ``pts`` for
            sensor files. Another common extension is ``ill`` for the results of daylight
            studies.
    """
    try:
        # handle optional case for Functions input
        if dist_info and not Path(dist_info).is_file():
            dist_info = None
        restore_original_distribution(
            input_folder, output_folder, extension, dist_info, output_extension,
            as_text, fmt, delimiter)
    except Exception:
        _logger.exception('Failed to restructure data from folder.')
        sys.exit(1)
    else:
        sys.exit(0)


@grid.command('merge-folder-metrics')
@click.argument(
    'input-folder',
    type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True))
@click.argument(
    'output-folder',
    type=click.Path(file_okay=False, dir_okay=True, resolve_path=True))
@click.option(
    '--dist-info', '-di',
    help='An optional input for distribution information to put the grids back together '
    '. Alternatively, the command will look for a _redist_info.json file inside the '
    'folder.', type=click.Path(file_okay=True, dir_okay=False, resolve_path=True)
)
@click.option(
    '--grids-info', '-gi',
    help='An optional input for grid information that will be copied to each '
    'metric folder. This file is usually called grids_info.json.',
    type=click.Path(file_okay=True, dir_okay=False, resolve_path=True)
)
def merge_metrics_folder(input_folder, output_folder, dist_info, grids_info):
    """Restructure annual daylight metrics in a distributed folder.
    
    Since this command redistributes metrics it is expected that the input
    folder has sub folder

    \b
    Args:
        input_folder: Path to input folder.
        output_folder: Path to the new restructured folder
    """
    try:
        # handle optional case for Functions input
        if dist_info and not Path(dist_info).is_file():
            dist_info = None
        if grids_info:
            with open(grids_info) as gi:
                grids_info = json.load(gi)
        extension_mapper = {
            'da': 'da',
            'cda': 'cda',
            'udi': 'udi',
            'udi_lower': 'udi',
            'udi_upper': 'udi'
        }
        metric_info_dict = _annual_daylight_vis_metadata()
        input_folder = Path(input_folder)
        output_folder = Path(output_folder)
        for metric, extension in extension_mapper.items():
            metric_folder = input_folder.joinpath(metric)
            metric_out = output_folder.joinpath(metric)
            restore_original_distribution_metrics(
                metric_folder, output_folder, metric, extension, dist_info)

            if grids_info:
                info_file = metric_out.joinpath('grids_info.json')
                info_file.write_text(json.dumps(grids_info))

            vis_data = metric_info_dict[metric]
            vis_metadata_file = metric_out.joinpath('vis_metadata.json')
            vis_metadata_file.write_text(json.dumps(vis_data, indent=4))
    except Exception:
        _logger.exception('Failed to restructure data from folder.')
        sys.exit(1)
    else:
        sys.exit(0)


[docs] def restore_original_distribution( input_folder, output_folder, extension='npy', dist_info=None, output_extension='ill', as_text=False, fmt='%.2f', input_delimiter=',', delimiter='tab'): """Restructure files to the original distribution based on the distribution info. It will assume that the files in the input folder are NumPy files. However, if it fails to load the files as arrays it will try to load from binary Radiance files to array. Args: input_folder: Path to input folder. output_folder: Path to the new restructured folder extension: Extension of the files to collect data from. Default is ``npy`` for NumPy files. Another common extension is ``ill`` for the results of daylight studies. dist_info: Path to dist_info.json file. If None, the function will try to load ``_redist_info.json`` file from inside the input_folder. (Default: None). output_extension: Output file extension. This is only used if as_text is set to True. Otherwise the output extension will be ```npy``. as_text: Set to True if the output files should be saved as text instead of NumPy files. fmt: Format for the output files when saved as text. input_delimiter: Delimiter for the input files. This is used only if the input files are text files. delimiter: Delimiter for the output files when saved as text. """ if not dist_info: _redist_info_file = Path(input_folder, '_redist_info.json') else: _redist_info_file = Path(dist_info) assert _redist_info_file.is_file(), 'Failed to find %s' % _redist_info_file with open(_redist_info_file) as inf: data = json.load(inf) # create output folder output_folder = Path(output_folder) if not output_folder.is_dir(): output_folder.mkdir(parents=True, exist_ok=True) src_file = Path() for f in data: output_file = Path(output_folder, f['identifier']) # ensure the new folder is created. in case the identifier has a subfolder parent_folder = output_file.parent if not parent_folder.is_dir(): parent_folder.mkdir() out_arrays = [] for src_info in f['dist_info']: st = src_info['st_ln'] end = src_info['end_ln'] new_file = Path(input_folder, '%s.%s' % (src_info['identifier'], extension)) if not new_file.samefile(src_file): src_file = new_file try: array = np.load(src_file) except: try: array = binary_to_array(src_file) except: try: array = np.loadtxt( src_file, delimiter=input_delimiter) except Exception: raise RuntimeError( f'Failed to load input file "{src_file}"') slice_array = array[st:end+1,:] out_arrays.append(slice_array) out_array = np.concatenate(out_arrays) # save numpy array, .npy extension is added automatically if not as_text: np.save(output_file, out_array) else: if output_extension.startswith('.'): output_extension = output_extension[1:] if delimiter == 'tab': delimiter = '\t' elif delimiter == 'space': delimiter = ' ' elif delimiter == 'comma': delimiter = ',' np.savetxt(output_file.with_suffix(f'.{output_extension}'), out_array, fmt=fmt, delimiter=delimiter)
[docs] def restore_original_distribution_metrics( input_folder, output_folder, metric, extension, dist_info=None): """Restructure files to the original distribution based on the distribution info. It will assume that the files in the input folder are NumPy files. However, if it fails to load the files as arrays it will try to load from binary Radiance files to array. Args: input_folder: Path to input folder. output_folder: Path to the new restructured folder metric: Name of the metric to redistribute. extension: Extension of the files to collect data from. For annual daylight metrics the extension can be 'da', 'cda', or 'udi'. dist_info: Path to dist_info.json file. If None, the function will try to load ``_redist_info.json`` file from inside the input_folder. (Default: None). """ if not dist_info: _redist_info_file = Path(input_folder, '_redist_info.json') else: _redist_info_file = Path(dist_info) assert _redist_info_file.is_file(), 'Failed to find %s' % _redist_info_file with open(_redist_info_file) as inf: data = json.load(inf) # create output folder output_folder = Path(output_folder) if not output_folder.is_dir(): output_folder.mkdir() src_file = Path() for f in data: output_file = Path(output_folder, metric, '%s.%s' % (f['identifier'], extension)) # ensure the new folder is created. in case the identifier has a subfolder parent_folder = output_file.parent if not parent_folder.is_dir(): parent_folder.mkdir() out_arrays = [] for src_info in f['dist_info']: st = src_info['st_ln'] end = src_info['end_ln'] new_file = Path(input_folder, '%s.%s' % (src_info['identifier'], extension)) if not new_file.samefile(src_file): src_file = new_file array = np.loadtxt(src_file) slice_array = array[st:end+1] out_arrays.append(slice_array) out_array = np.concatenate(out_arrays) # save array as txt file np.savetxt(output_file, out_array, fmt='%.2f')