Skip to content

Commit

Permalink
feat(pinterp): Implemented pinterp command and options (#204)
Browse files Browse the repository at this point in the history
* feat(pinterp): Implemented pinterp command and options

* test(pinterp): Fixing typo

* docs(pinterp): Make docstrings more helpful
  • Loading branch information
mikkelkp authored Nov 16, 2021
1 parent 5b4a720 commit 3c25024
Show file tree
Hide file tree
Showing 3 changed files with 364 additions and 0 deletions.
149 changes: 149 additions & 0 deletions honeybee_radiance_command/options/pinterp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# coding: utf-8

from .optionbase import (
OptionCollection,
BoolOption,
StringOption,
IntegerOption,
NumericOption,
)


class PinterpOptions(OptionCollection):
"""pinterp command options.
Also see: https://floyd.lbl.gov/radiance/man_html/pinterp.1.html
"""

__slots__ = (
"_vf",
"_x",
"_y",
"_t",
"_fa",
"_ff",
"_fb",
"_f0",
"_n",
"_e",
)

def __init__(self):
"""pinterp command options."""
OptionCollection.__init__(self)
self._vf = StringOption("vf", "view options")
self._x = IntegerOption("x", "x resolution")
self._y = IntegerOption("y", "y resolution")
self._t = NumericOption("t", "threshold for coincident pixels - default: 0.02")
self._fa = BoolOption("fa", "foreground and background filling")
self._ff = BoolOption("ff", "foreground filling")
self._fb = BoolOption("fb", "background filling")
self._f0 = BoolOption("f0", "no fill algorithm")
self._n = BoolOption("n", "z distances along view direction")
self._e = NumericOption("e", "exposure adjustment multiplier - default: 1")

@property
def vf(self):
"""View options
Set the view to extract.
"""
return self._vf

@vf.setter
def vf(self, value):
self._vf.value = value

@property
def x(self):
"""x resolution
Set the maximum x resolution.
"""
return self._x

@x.setter
def x(self, value):
self._x.value = value

@property
def y(self):
"""y resolution
Set the maximum y resolution.
"""
return self._y

@y.setter
def y(self, value):
self._y.value = value

@property
def t(self):
"""Threshold for coincident pixels - default: 0.02
Pixels that map within the −t threshold of each other (.02 times the z distance
by default) are considered coincident.
"""
return self._t

@t.setter
def t(self, value):
self._t.value = value

@property
def fa(self):
"""Enable foreground and background filling"""
return self._fa

@fa.setter
def fa(self, value):
self.fa.value = value

@property
def ff(self):
"""Enable foreground filling"""
return self._ff

@ff.setter
def ff(self, value):
self.ff.value = value

@property
def fb(self):
"""Enable background filling"""
return self._fb

@fb.setter
def fb(self, value):
self.fb.value = value

@property
def f0(self):
"""Disable filling algorithm"""
return self._f0

@f0.setter
def f0(self, value):
self.f0.value = value

@property
def n(self):
"""specifies that input and output z distances are along the view direction,
rather than absolute distances to intersection points. This option is usually
appropriate with a constant z specification, and should not be used with rpict
z files."""
return self._n

@n.setter
def n(self, value):
self.n.value = value

@property
def e(self):
"""Exposure adjustment multiplier - default: 1"""
return self._e

@e.setter
def e(self, value):
self._e.value = value
144 changes: 144 additions & 0 deletions honeybee_radiance_command/pinterp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
"""pinterp command."""

from .options.pinterp import PinterpOptions
from ._command import Command
import honeybee_radiance_command._exception as exceptions
import honeybee_radiance_command._typing as typing


class Pinterp(Command):
"""Pinterp command.
Pinterp interpolates or extrapolates a new view from one or more RADIANCE pictures
and sends the result to the standard output.
Args:
options: Command options. It will be set to Radiance default values
if unspecified.
output: File path to the output file (Default: None).
view: File path to a view file. This is the view to interpolate or extrapolate
(Default: None).
image: Radiance HDR image(s) to interpolate or extrapolate from. A list of images
can be given if multiple images are used (Default: None).
zspec: The distance from the view point to each pixel in the image(s). Typically
this input is generated as a file by using the -z option of rpict. A number
can also be given instead, which should only be used if the view point remains
constanst, e.g., if the view point in a single input image is equal to that
of the input view. If a list of images is given, then the zspec must also be
a list matching the length of the list of images. (Default: None).
Properties:
* options
* output
* view
* image
* zspec
"""

__slots__ = ('_view', '_image', '_zspec')

def __init__(
self, options=None, output=None, view=None, image=None, zspec=None):
"""Initialize Command."""
Command.__init__(self, output=output)
self.options = options
self.view = view
self.image = image
self.zspec = zspec

@property
def options(self):
"""pinterp options."""
return self._options

@options.setter
def options(self, value):
if not value:
value = PinterpOptions()

if not isinstance(value, PinterpOptions):
raise ValueError('Expected Pinterp options not {}'.format(value))

self._options = value

@property
def view(self):
"""View to interpolate or extrapolate."""
return self._view

@view.setter
def view(self, value):
if not value:
self._view = None
else:
self._view = typing.normpath(value)

@property
def image(self):
"""Radiance HDR image file(s)."""
return self._image

@image.setter
def image(self, value):
if not isinstance(value, (list, tuple)):
self._image = [value]
else:
self._image = value

@property
def zspec(self):
"""z specification for input image(s)"""
return self._zspec

@zspec.setter
def zspec(self, value):
if not isinstance(value, (list, tuple)):
self._zspec = [value]
else:
self._zspec = value

def to_radiance(self, stdin_input=False):
"""Command in Radiance format.
Args:
stdin_input: A boolean that indicates if the input for this command
comes from stdin. This is for instance the case when you pipe the input
from another command. (Default: False).
"""
self.validate(stdin_input)

# length of images and zpec must be the same
if len(self.image) != len(self.zspec):
raise ValueError(
'Pinterp command needs each input image to be accompanied by a'
' z specification. Found {} image(s) and {} z specification(s).'
' Make sure the number of images are equal to the number of'
' z specifications'.format(len(self.image), len(self.zspec)))

if stdin_input:
self.options.vf = '-'
else:
self.options.vf = self.view

command_parts = [self.command, self.options.to_radiance()]
cmd = ' '.join(command_parts)

for (img, z) in zip(self.image, self.zspec):
cmd = '%s %s %s' % (cmd, img, z)

if self.pipe_to:
cmd = '%s | %s' % (cmd, self.pipe_to.to_radiance(stdin_input=True))

elif self.output:
cmd = '%s > %s' % (cmd, self.output)

return ' '.join(cmd.split())

def validate(self, stdin_input=False):
Command.validate(self)
if not stdin_input and not self.view:
raise exceptions.MissingArgumentError(self.command, 'view')
if not self.image[0]:
raise exceptions.MissingArgumentError(self.command, 'image')
if not self.zspec[0]:
raise exceptions.MissingArgumentError(self.command, 'zspec')
71 changes: 71 additions & 0 deletions tests/pinterp_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from honeybee_radiance_command.pinterp import Pinterp
import pytest
import honeybee_radiance_command._exception as exceptions


def test_defaults():
"""Test command."""
pinterp = Pinterp()

assert pinterp.command == 'pinterp'
assert pinterp.options.to_radiance() == ''


def test_assignment():
"""Test assignments."""
pinterp = Pinterp()

pinterp.view = 'view.vf'
assert pinterp.view == 'view.vf'
pinterp.image = 'image.hdr'
assert pinterp.image == ['image.hdr']
pinterp.output = 'output.hdr'
assert pinterp.output == 'output.hdr'
pinterp.zspec = 1
assert pinterp.zspec == [1]
assert pinterp.to_radiance() == 'pinterp -vf view.vf image.hdr 1 > output.hdr'


def test_options():
"""Test assignment of some of the options."""
pinterp = Pinterp()

pinterp.view = 'view.vf'
pinterp.image = 'image.hdr'
pinterp.zspec = 1
pinterp.options.ff = True
pinterp.options.x = 800
pinterp.options.y = 800
assert pinterp.to_radiance() == 'pinterp -ff -vf view.vf -x 800 -y 800 image.hdr 1'


def test_stdin():
"""Test stdin."""
pinterp = Pinterp()

pinterp.image = 'image.hdr'
pinterp.output = 'output.hdr'
pinterp.zspec = 1
assert pinterp.to_radiance(stdin_input=True) == 'pinterp -vf - image.hdr 1 > output.hdr'


def test_validation():
"""Test if errors are raised on missing arguments."""
pinterp = Pinterp()

with pytest.raises(exceptions.MissingArgumentError):
# missing view
pinterp.to_radiance()

pinterp.view = 'view.vf'
with pytest.raises(exceptions.MissingArgumentError):
# missing image
pinterp.to_radiance()

pinterp.image = 'image.hdr'
with pytest.raises(exceptions.MissingArgumentError):
# missing zpec
pinterp.to_radiance()

pinterp.zspec = 1
assert pinterp.to_radiance() == 'pinterp -vf view.vf image.hdr 1'

0 comments on commit 3c25024

Please sign in to comment.