"""Radiance Daylight Coefficient Image-Based Analysis Recipe."""
from ..recipeutil import write_extra_files, glz_srf_to_window_group
from ..recipedcutil import write_rad_files_daylight_coeff, create_reference_map_command
from ..recipedcutil import image_based_view_sampling_commands, \
image_based_view_coeff_matrix_commands, imaged_based_sun_coeff_matrix_commands
from ...command.oconv import Oconv
from ...command.pcomb import Pcomb, PcombImage
from ...parameters.pcomb import PcombParameters
from ..parameters import get_radiance_parameters_image_based
from ..recipedcutil import image_based_view_matrix_calculation
from ..recipedcutil import sky_receiver, get_commands_sky
from .._imagebasedbase import GenericImageBased
from ...parameters.vwrays import VwraysParameters
from ...resultcollection.imagecollection import ImageCollection
from ....futil import write_to_file
import os
try:
from itertools import izip as zip
except:
# python 3
pass
[docs]class DaylightCoeffImageBased(GenericImageBased):
"""Daylight Coefficient Image-Based.
Attributes:
sky_mtx: A honeybee sky for the analysis
views: List of views.
simulation_type: 0: Illuminance(lux), 1: Radiation (kWh), 2: Luminance (Candela)
(Default: 2)
rad_parameters: Radiance parameters for grid based analysis (rtrace).
(Default: imagebased.LowQualityImage)
hb_objects: An optional list of Honeybee surfaces or zones (Default: None).
sub_folder: Analysis subfolder for this recipe. (Default: "gridbased")
Usage:
"""
# TODO: implemnt isChanged at AnalysisRecipe level to reload the results
# if there has been no changes in inputs.
def __init__(self, sky_mtx, views, simulation_type=2, daylight_mtx_parameters=None,
vwrays_parameters=None, reuse_daylight_mtx=True, hb_objects=None,
sub_folder="imagebased_daylightcoeff"):
"""Create grid-based recipe."""
GenericImageBased.__init__(
self, views, hb_objects, sub_folder)
self.sky_matrix = sky_mtx
"""A honeybee sky for the analysis."""
self.simulation_type = simulation_type
"""Simulation type: 0: Illuminance(lux), 1: Radiation (kWh),
2: Luminance (Candela) (Default: 2)
"""
self.daylight_mtx_parameters = daylight_mtx_parameters
"""Radiance parameters for image based analysis (rfluxmtx).
(Default: imagebased.LowQualityImage)"""
self.vwrays_parameters = vwrays_parameters
"""Radiance parameters for vwrays.
(Default: imagebased.LowQualityImage)"""
self.reuse_daylight_mtx = reuse_daylight_mtx
@property
def simulation_type(self):
"""Get/set simulation Type.
0: Illuminance(lux), 1: Radiation (kWh), 2: Luminance (Candela) (Default: 0)
"""
return self._simType
@simulation_type.setter
def simulation_type(self, value):
try:
value = int(value)
except TypeError:
value = 0
assert 0 <= value <= 2, \
"Simulation type should be between 0-2. Current value: {}".format(value)
# If this is a radiation analysis make sure the sky is climate-based
if value == 1:
assert self.sky_matrix.is_climate_based, \
"The sky for radition analysis should be climate-based."
self._simType = value
self.sky_matrix.sky_type = value
@property
def sky_matrix(self):
"""Get and set sky definition."""
return self._sky_matrix
@sky_matrix.setter
def sky_matrix(self, new_sky):
assert hasattr(new_sky, 'isRadianceSky'), \
'%s is not a valid Honeybee sky.' % type(new_sky)
assert not new_sky.is_point_in_time, \
TypeError('Sky for daylight coefficient recipe must be a sky matrix.')
self._sky_matrix = new_sky.duplicate()
@property
def sky_density(self):
"""Radiance sky type e.g. r1, r2, r4."""
return "r{}".format(self.sky_matrix.sky_density)
@property
def daylight_mtx_parameters(self):
"""Get and set Radiance parameters."""
return self._daylight_mtx_parameters
@daylight_mtx_parameters.setter
def daylight_mtx_parameters(self, par):
if not par:
# set RfluxmtxParameters as default radiance parameter for annual analysis
par = get_radiance_parameters_image_based(0, 1).dmtx
else:
assert hasattr(par, 'isRfluxmtxParameters'), \
TypeError('Expected RfluxmtxParameters not {}'.format(type(par)))
self._daylight_mtx_parameters = par
@property
def vwrays_parameters(self):
"""Get and set Radiance parameters."""
return self._vwrays_parameters
@vwrays_parameters.setter
def vwrays_parameters(self, par):
if not par:
# set VwraysParameters as default radiance parameter for annual analysis
par = VwraysParameters()
par.sampling_rays_count = self.daylight_mtx_parameters.sampling_rays_count
else:
assert hasattr(par, 'isVwraysParameters'), \
TypeError('Expected VwraysParameters not {}'.format(type(par)))
assert par.sampling_rays_count == \
self.daylight_mtx_parameters.sampling_rays_count, \
ValueError(
'Number of sampling_rays_count should be equal between '
'daylight_mtx_parameters [{}] and vwrays_parameters [{}].'
.format(self.daylight_mtx_parameters.sampling_rays_count,
par.sampling_rays_count))
self._vwrays_parameters = par
[docs] def is_daylight_mtx_created(self, study_folder, view, wg, state):
"""Check if hdr images for daylight matrix are already created."""
for i in range(1 + 144 * (self.sky_matrix.sky_density ** 2)):
for t in ('total', 'direct'):
fp = os.path.join(
study_folder, 'result/dc/%s/%03d_%s..%s..%s.hdr' % (
t, i, view.name, wg.name, state.name)
)
if not os.path.isfile(fp) or os.path.getsize(fp) < 265:
# file doesn't exist or is smaller than 265 bytes
return False
return True
[docs] def is_sun_mtx_created(self, study_folder, view, wg, state):
"""Check if hdr images for daylight matrix are already created."""
for count, h in enumerate(self.sky_matrix.hoys):
fp = os.path.join(
study_folder, 'result/dc/{}/{}_{}..{}..{}.hdr'.format(
'isun', '%04d' % count, view.name, wg.name, state.name))
if not os.path.isfile(fp) or os.path.getsize(fp) < 265:
# file doesn't exist or is smaller than 265 bytes
return False
return True
[docs] def is_hdr_mtx_created(self, study_folder, view, wg, state, stype):
"""Check if hourly hdr images for daylight matrix are already created."""
for count, h in enumerate(self.sky_matrix.hoys):
fp = os.path.join(
study_folder, 'result/hdr/{}/{}_{}..{}..{}.hdr'.format(
stype, '%04d' % (count + 1), view.name, wg.name, state.name))
if not os.path.isfile(fp) or os.path.getsize(fp) < 265:
# file doesn't exist or is smaller than 265 bytes
return False
return True
[docs] def write(self, target_folder, project_name='untitled', header=True):
"""Write analysis files to target folder.
Args:
target_folder: Path to parent folder. Files will be created under
target_folder/gridbased. use self.sub_folder to change subfolder name.
project_name: Name of this project as a string.
Returns:
Full path to command.bat
"""
# 0.prepare target folder
self._commands = []
# create main folder target_folder/project_name
project_folder = \
super(GenericImageBased, self).write_content(
target_folder, project_name, False,
subfolders=['view', 'result/dc', 'result/hdr',
'result/dc/total', 'result/dc/direct', 'result/dc/isun',
'result/dc/refmap', 'result/hdr/total', 'result/hdr/direct',
'result/hdr/sun', 'result/hdr/combined', 'result/hdr/isun']
)
# write geometry and material files
opqfiles, glzfiles, wgsfiles = write_rad_files_daylight_coeff(
project_folder + '/scene', project_name, self.opaque_rad_file,
self.glazing_rad_file, self.window_groups_rad_files
)
# additional radiance files added to the recipe as scene
extrafiles = write_extra_files(self.scene, project_folder + '/scene', True)
# reset self.result_files
self._result_files = [[] for v in xrange(self.view_count)]
# 1.write views
view_files = self.write_views(project_folder + '/view')
if header:
self.commands.append(self.header(project_folder))
# # 2.1.Create sky matrix.
# # 2.2. Create sun matrix
skycommands, skyfiles = get_commands_sky(project_folder, self.sky_matrix,
reuse=True)
sky_mtx_total, sky_mtx_direct, analemma, sunlist, analemmaMtx = skyfiles
self._commands.extend(skycommands)
# for each window group - calculate total, direct and direct-analemma results
# I can just add fenestration rad files here and that will work!
# calculate the contribution of glazing if any with all window groups blacked
# this is a hack. A better solution is to create a HBDynamicSurface from glazing
# surfaces. The current limitation is that HBDynamicSurface can't have several
# surfaces with different materials.
all_window_groups = [glz_srf_to_window_group()]
all_window_groups.extend(self.window_groups)
all_wgs_files = [glzfiles] + list(wgsfiles)
# create the base octree for the scene
# TODO(mostapha): this should be fine for most of the cases but
# if one of the window groups has major material change in a step
# that won't be included in this step.
# add material file
try:
blkmaterial = [wgsfiles[0].fpblk[0]]
except IndexError:
# no window groups
blkmaterial = []
# add all the blacked window groups but the one in use
# and finally add non-window group glazing as black
wgsblacked = [f.fpblk[1] for f in wgsfiles] + list(glzfiles.fpblk)
scene_files = [f for filegroups in (opqfiles.fp, glzfiles.fp, extrafiles.fp)
for f in filegroups]
oct_scene_files = scene_files + blkmaterial + wgsblacked
oc = Oconv(project_name)
oc.scene_files = tuple(self.relpath(f, project_folder)
for f in oct_scene_files)
self._commands.append(oc.to_rad_string())
# # 4.2.prepare vwray
for viewCount, (view, view_file) in enumerate(zip(self.views, view_files)):
# create the reference map file
self.commands.append(':: calculation for view: {}'.format(view.name))
self.commands.append(':: 0 reference map')
refmapfilename = '{}_map.hdr'.format(view.name)
refmapfilepath = os.path.join('result/dc/refmap', refmapfilename)
if not self.reuse_daylight_mtx or not os.path.isfile(
os.path.join(project_name, 'result/dc/refmap', refmapfilename)):
rfm = create_reference_map_command(
view, self.relpath(view_file, project_folder),
'result/dc/refmap', oc.output_file)
self._commands.append(rfm.to_rad_string())
# Step1: Create the view matrix.
self.commands.append(':: 1 view sampling')
view_info_file, vwr_samp = image_based_view_sampling_commands(
project_folder, view, view_file, self.vwrays_parameters)
self.commands.append(vwr_samp.to_rad_string())
# set up the geometries
for count, wg in enumerate(all_window_groups):
if count == 0:
if len(wgsfiles) > 0:
blkmaterial = [wgsfiles[0].fpblk[0]]
wgsblacked = [f.fpblk[1] for c, f in enumerate(wgsfiles)]
else:
blkmaterial = []
wgsblacked = []
else:
# add material file
blkmaterial = [all_wgs_files[count].fpblk[0]]
# add all the blacked window groups but the one in use
# and finally add non-window group glazing as black
wgsblacked = \
[f.fpblk[1] for c, f in enumerate(wgsfiles)
if c != count - 1] + list(glzfiles.fpblk)
for scount, state in enumerate(wg.states):
# 2.3.Generate daylight coefficients using rfluxmtx
# collect list of radiance files in the scene for both total
# and direct
self._commands.append(
'\n:: calculation for {} window group {}'.format(wg.name,
state.name))
if count == 0:
# normal glazing
non_blacked_wgfiles = all_wgs_files[count].fp
else:
non_blacked_wgfiles = (all_wgs_files[count].fp[scount],)
rflux_scene = (
f for fl in
(non_blacked_wgfiles, opqfiles.fp, extrafiles.fp,
blkmaterial, wgsblacked)
for f in fl)
rflux_scene_blacked = (
f for fl in
(non_blacked_wgfiles, opqfiles.fpblk, extrafiles.fpblk,
blkmaterial, wgsblacked)
for f in fl)
sender = '-'
ground_file_format = 'result/dc/total/%03d_%s..%s..%s.hdr' % (
1 + 144 * (self.sky_matrix.sky_density ** 2),
view.name, wg.name, state.name
)
sky_file_format = 'result/dc/total/%03d_{}..{}..{}.hdr'.format(
view.name, wg.name, state.name)
receiver = sky_receiver(
os.path.join(
project_folder,
'sky/rfluxSkyTotal..{}..{}.rad'.format(
wg.name, state.name)),
self.sky_matrix.sky_density, ground_file_format, sky_file_format
)
ground_file_format = 'result/dc/direct/%03d_%s..%s..%s.hdr' % (
1 + 144 * (self.sky_matrix.sky_density ** 2),
view.name, wg.name, state.name
)
sky_file_format = 'result/dc/direct/%03d_{}..{}..{}.hdr'.format(
view.name, wg.name, state.name)
receiver_dir = sky_receiver(
os.path.join(project_folder,
'sky/rfluxSkyDirect..{}..{}.rad'.format(
wg.name, state.name)),
self.sky_matrix.sky_density, ground_file_format, sky_file_format
)
rad_files_blacked = tuple(self.relpath(f, project_folder)
for f in rflux_scene_blacked)
# Daylight matrix
if not self.reuse_daylight_mtx or not \
self.is_daylight_mtx_created(project_folder, view, wg,
state):
self.reuse_daylight_mtx = False
rad_files = tuple(self.relpath(f, project_folder)
for f in rflux_scene)
self._commands.append(
':: :: 1. daylight matrix {}, {} > state {}'.format(
view.name, wg.name, state.name)
)
self._commands.append(':: :: 1.1 scene daylight matrix')
# output pattern is set in receiver
rflux = image_based_view_coeff_matrix_commands(
self.relpath(receiver, project_folder),
rad_files, sender, view_info_file,
view_file, str(vwr_samp.output_file),
self.daylight_mtx_parameters)
self.commands.append(rflux.to_rad_string())
else:
print(
'reusing the dalight matrix for {}:{} from '
'the previous study.'.format(wg.name, state.name))
if not self.reuse_daylight_mtx or not \
self.is_sun_mtx_created(project_folder, view, wg, state):
self._commands.append(':: :: 1.2 blacked scene daylight matrix')
ab = int(self.daylight_mtx_parameters.ambient_bounces)
self.daylight_mtx_parameters.ambient_bounces = 1
# output pattern is set in receiver
rflux_direct = image_based_view_coeff_matrix_commands(
self.relpath(receiver_dir, project_folder),
rad_files_blacked, sender, view_info_file,
view_file, str(vwr_samp.output_file),
self.daylight_mtx_parameters)
self._commands.append(rflux_direct.to_rad_string())
self._commands.append(':: :: 1.3 blacked scene analemma matrix')
if os.name == 'nt':
output_filename_format = \
' result/dc/isun/%%04d_{}..{}..{}.hdr'.format(
view.name, wg.name, state.name)
else:
output_filename_format = \
' result/dc/isun/%04d_{}..{}..{}.hdr'.format(
view.name, wg.name, state.name)
sun_commands = imaged_based_sun_coeff_matrix_commands(
output_filename_format, view, str(vwr_samp.output_file),
rad_files_blacked, self.relpath(analemma, project_folder),
sunlist)
# delete the files if they are already created
# rcontrib won't overwrite the files if they already exist
for hourcount in xrange(len(self.sky_matrix.hoys)):
sf = 'result/dc/isun/{:04d}_{}..{}..{}.hdr'.format(
hourcount, view.name, wg.name, state.name
)
try:
fp = os.path.join(project_folder, sf)
os.remove(fp)
except Exception as e:
# failed to delete the file
if os.path.isfile(fp):
print('Failed to remove {}:\n{}'.format(sf, e))
self._commands.extend(cmd.to_rad_string()
for cmd in sun_commands)
self.daylight_mtx_parameters.ambient_bounces = ab
else:
print(
'reusing the sun matrix for {}:{} from '
'the previous study.'.format(wg.name, state.name))
# generate hourly images
if skycommands or not self.reuse_daylight_mtx or not \
self.is_hdr_mtx_created(project_folder, view, wg,
state, 'total'):
# Generate resultsFile
self._commands.append(
':: :: 2.1.0 total daylight matrix calculations')
dct = image_based_view_matrix_calculation(
view, wg, state, sky_mtx_total, 'total')
self.commands.append(dct.to_rad_string())
else:
print(
'reusing the total dalight matrix for {}:{} from '
'the previous study.'.format(wg.name, state.name))
if skycommands or not self.reuse_daylight_mtx or not \
self.is_hdr_mtx_created(project_folder, view, wg,
state, 'direct'):
self._commands.append(':: :: 2.2.0 direct matrix calculations')
dct_direct = image_based_view_matrix_calculation(
view, wg, state, sky_mtx_direct, 'direct')
self._commands.append(dct_direct.to_rad_string())
else:
print(
'reusing the direct dalight matrix for {}:{} from '
'the previous study.'.format(wg.name, state.name))
if skycommands or not self.reuse_daylight_mtx or not \
self.is_hdr_mtx_created(project_folder, view, wg,
state, 'sun'):
self._commands.append(
':: :: 2.3.0 enhanced direct matrix calculations')
dct_sun = image_based_view_matrix_calculation(
view, wg, state,
self.relpath(analemmaMtx, project_folder), 'isun', 4)
self._commands.append(dct_sun.to_rad_string())
# multiply the sun matrix with the reference map
# TODO: move this to a separate function
# TODO: write the loop as a for loop in bat/bash file
par = PcombParameters()
refmap_image = PcombImage(input_image_file=refmapfilepath)
if os.name == 'nt':
par.expression = '"lo=li(1)*li(2)"'
else:
par.expression = "'lo=li(1)*li(2)'"
for hourcount in xrange(len(self.sky_matrix.hoys)):
inp = 'result/hdr/isun/{:04d}_{}..{}..{}.hdr'.format(
hourcount + 1, view.name, wg.name, state.name
)
out = 'result/hdr/sun/{:04d}_{}..{}..{}.hdr'.format(
hourcount + 1, view.name, wg.name, state.name
)
images = PcombImage(input_image_file=inp), refmap_image
pcb = Pcomb(images, out, par)
self._commands.append(pcb.to_rad_string())
else:
print(
'reusing the enhanced direct dalight matrix for '
'{}:{} from the previous study.'
.format(wg.name, state.name))
result_files = tuple(os.path.join(
project_folder,
'result/hdr/%s/{}_{}..{}..{}.hdr'.format(
'%04d' % (count + 1), view.name, wg.name, state.name))
for count, h in enumerate(self.sky_matrix.hoys)
)
self._result_files[viewCount].append(
(wg.name, state.name, result_files))
# # 4.3 write batch file
batch_file = os.path.join(project_folder, "commands.bat")
write_to_file(batch_file, "\n".join(self.commands))
print("Files are written to: %s" % project_folder)
return batch_file
[docs] def results(self):
"""Return results for this analysis."""
assert self._isCalculated, \
"You haven't run the Recipe yet. Use self.run " + \
"to run the analysis before loading the results."
hoys = self.sky_matrix.hoys
for viewCount, viewResults in enumerate(self._result_files):
imgc = ImageCollection(self.views[viewCount].name)
for source, state, files in viewResults:
source_files = []
for i in files:
fpt = i % 'total'
fpd = i % 'direct'
fps = i % 'sun'
source_files.append((fpt, fpd, fps))
imgc.add_coupled_image_files(source_files, hoys, source, state)
# TODO(mostapha): Add properties to the class for output file addresses
imgc.output_folder = fpt.split('result/hdr')[0] + 'result/hdr/combined'
yield imgc
[docs] def ToString(self):
"""Overwrite .NET ToString method."""
return self.__repr__()
def __repr__(self):
"""Represent grid based recipe."""
_analysisType = {
0: "Illuminance", 1: "Radiation", 2: "Luminance"
}
return "%s: %s\n#Views: %d" % \
(self.__class__.__name__,
_analysisType[self.simulation_type],
self.view_count)