-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(pinterp): Implemented pinterp command and options (#204)
* feat(pinterp): Implemented pinterp command and options * test(pinterp): Fixing typo * docs(pinterp): Make docstrings more helpful
- Loading branch information
Showing
3 changed files
with
364 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' |