—# documentation for bern.pm module / 2019-12-30
=encoding utf8
=head1 NAME
ICC::Support::bern - An object oriented module for modeling transfer functions (curves) using Bernstein polynomials.
=head1 SYNOPSIS
use ICC::Support::bern;
# create a new object
$bern = ICC::Support::bern->new(); # empty object
$bern = ICC::Support::bern->new($hash); # from a hash
# create an inverse object
$inv = $bern->inv(); # object is cloned, then inverted
# get/set header hash
$hash = $bern->header(); # get header hash reference
$hash = $bern->header($replacement_hash_ref); # set header hash
# get/set input parameters
$input = $bern->input(); # get input array reference
$input = $bern->input($replacement_array_ref); # set input array
# get/set output parameters
$output = $bern->output(); # get output array reference
$output = $bern->output($replacement_array_ref); # set output array
# transforms
$out = $bern->transform($in);
$out = $bern->inverse($in);
$out = $bern->derivative($in);
@pd = $bern->parametric($in);
# directional transforms
$out = $bern->_transform($dir, $in);
$out = $bern->_derivative($dir, $in);
@pd = $bern->_parametric($dir, $in);
# normalize parameters
$bern = $bern->normalize();
$bern = $bern->normalize($hash);
# min/max values
@minmax = $bern->monotonic();
@minmax = $bern->monotonic([$format]);
# update object internals
$bern->update();
# make LUT for profile objects
$table = $bern->table($n);
$table = $bern->table($n, $dir);
# make equivalent 'curv' object
$curv = $bern->curv($n);
$curv = $bern->curv($n, $dir);
# dump object
$string = $bern->sdump([$format]);
$bern->dump([$format]);
=head1 DESCRIPTION
Bernstein polynomials are ideal for modeling a function over a limited range. These polynomials are a linear
combination of Bernstein basis functions. There are n + 1 basis functions in a Bernstein set of degree n. The
polynomial coefficients determine the shape of the curve, and are stored within the object. The degree of the
basis functions determines the possible complexity of the curve.
A C<ICC::Support::bern> object contains coefficients for two curves, referred to as 'input' and 'output'. A
transform is done in two steps. First, the input value is transformed to an intermediate value, t, using the
'input' curve. Then, the t value is transformed to the output value using the 'output' curve. The direction of
the transform, forward or reverse, is easily changed by swapping the 'input' and 'output' curves.
=head2 Object structure
An C<ICC::Support::bern> object is a blessed array reference. The array contains four elements, the header hash,
the parameter array, the min/max x-values and the min/max y-values.
# create empty 'bern' object
my $self = [
{}, # header hash
[], # parameter array (for 'input' and 'output' curves)
[], # min/max t-values (for 'input' and 'output' curves)
[] # min/max i/o-values (for 'input' and 'output' curves)
];
The header hash contains metadata, and may be used to attach information to the object. The object uses the
key 'curv_points'. The user may add other key/value pairs.
The parameter array contains two arrays. The first holds the parameters of the 'input' curve, and the second
holds the parameters of the 'output' curve. The degree of these curves may be different. If an array is empty,
that side of the transform is disabled.
$self->[1][0] = [c0, c1, ... cm] # m + 1 coefficients for 'input' curve of degree m
$self->[1][1] = [c0, c1, ... cn] # n + 1 coefficients for 'output' curve of degree n
Likewise, the min/max arrays each contain arrays for the 'input' and 'output' curves. When either curve is not
monotonic, these arrays will contain the t-values and i/o-values of the minimum/maximum points. These values
are used to compute inverse functions. These arrays will be empty if the curves are monotonic.
=head2 Domain and range
An C<ICC::Support::bern> object contains two curves named 'input' and 'output'. These curves are Bernstein
polynomials. The domain of a Bernstein polynomial is always 0 to 1. The range depends on the parameters, and is
not restricted. If both curves are present, the 'transform' function will take an input value, perform an inverse
transform using the 'input' curve, then perform a normal transform using the 'output' curve. The intermediate
value between these steps is known as the t-value.
input_value => [inverse 'input' transform] => t-value => [regular 'output' transform] => output_value
The domain and range of the combined curve depends on the curve parameters.
If the object only contains an 'output' curve, the transform is simpler,
input_value => [regular 'output' transform] => output_value
The input value is now the t-value, so the domain is 0 to 1.
If the object only contains an 'input' curve, the transform is similar,
input_value => [inverse 'input' transform] => output_value
The output value is now the t-value, so the range is 0 to 1.
When the transform methods receive an input value outside the Bernstein domain, it is linearly extrapolated,
using the slope and value of the nearest endpoint.
=head1 METHODS
=head2 Creating ICC::Support::bern objects
=head3 new
This method creates an C<ICC::Support::bern> object.
With no parameters, the object contains the empty basic structure (see L<"Object structure">).
An object is normally created from a hash, as illustrated below.
B<Usage>
$bern = ICC::Support::bern->new(); # empty object
$bern = ICC::Support::bern->new($hash); # from a hash
B<Examples>
use ICC::Support::bern;
$bern = ICC::Support::bern->new(); # empty object
$bern = ICC::Support::bern->new({}); # default object (see notes 1 and 2)
$bern = ICC::Support::bern->new({'input' => []}); # disable 'input' curve (see note 3)
$bern = ICC::Support::bern->new({'output' => []}); # disable 'output' curve (see note 3)
$bern = ICC::Support::bern->new({'input' => [0, 100]}); # make linear 'input' curve (see note 4)
$bern = ICC::Support::bern->new({'output' => [0, 100]}); # make linear 'output' curve (see note 4)
$bern = ICC::Support::bern->new({'input' => [-1, 1], 'output' => [5, 10]}); # make linear curve (see note 5)
$bern = ICC::Support::bern->new({'output' => [0, 0.2, 0.7, 1]}); # make cubic 'output' curve (see note 6)
$bern = ICC::Support::bern->new({'output' => 4}); # make degree 4 linear 'output' curve (see note 7)
$x = [0, 0.1, 0.7, 0.23, 0.5]; # x-values
$y = [0.02, 0.17, 0.71, 0.19, 0.44]; # y-values
$bern = ICC::Support::bern->new({'output' => [(undef) x 4], 'fit' => [$x, $y]}); # fit curve to data (see note 8)
$bern = ICC::Support::bern->new({'output' => [0, (undef) x 3], 'fit' => [$x, $y]}); # fit curve to data (see note 9)
$bern = ICC::Support::bern->new({'output' => [(undef) x 4], 'fit' => [$x, $y], 'fix_sh' => 1}); # fit curve to data (see note 10)
=over
=item 1
An empty hash (C<{}>) makes an object with the input and output curves disabled. This object will transform
values unchanged (identity transform).
=item 2
Hash keys are C<'input'>, C<'output'>, C<'fit'>, C<'fix_hl'>, and C<'fix_sh'>
Hash values for C<'input'> and C<'output'> are either an integer, or a vector.
Hash value for C<'fit'> is a reference to an array containing a set of points as x and y vectors.
Hash values for C<'fix_hl'> and C<'fix_sh'> are boolean (0 or 1).
=item 3
Setting the coefficient array to C<[]> disables that curve for transforms (default). Transform values for that
curve are passed through unchanged. These objects are identical to the default object, and are intended just
for illustration.
=item 4
Setting the coefficient array to C<[a, b]> results in a linear transform where an input/output value of 'a' maps
to an intermediate 't' value of 0, and an input/output value of 'b' maps to an intermediate 't' value of 1.
=item 5
This example makes an object where an input value of -1 maps to 5, and a value of 1 maps to 10. Internally,
this is a two step transform; -1 maps to a 't' value of 0, which then maps to 5; 1 maps to a 't' value of 1,
which then maps to 10.
=item 6
This example makes an object with a degree 3 C<'output'> curve. The Bernstein coefficients are the vector elements.
The domain and range of this object are both (0 - 1). Values outside the range are linearly extrapolated
using the slope at the endpoints. Within the domain and range, the transform is a cubic polynomial.
=item 7
This example makes an object with a degree 4 'output' curve. The Bernstein coefficients are set to the
vector C<[0/4, 1/4, 2/4, 3/4, 4/4]>. This curve is the identity function, often called a 'linear' curve.
The Bernstein coefficients can now be modified (optimized) to achieve some goal.
=item 8
This example make an object with a degree 3 C<'output'> curve fitted to x-y data. All the Bernstein
coefficients are adjusted for the best (least squares) fit. This is set by the notation 'C<(undef) x 4>',
which results in 4 undefined 'output' coefficients, prior to the fitting process.
=item 9
This example is the same the previous one, except the first C<'output'> coefficient is set to 0, followed
by 3 undefined coefficients. Only these coefficients are fitted; the first one remains fixed at 0.
=item 10
This example fits the parameters to the x-y data with the C<fix_sh> flag set. If the resutling curve is
non-monotonic near t = 1, the slope at t = 1 may be opposite the main part of the curve. If that occurs,
the curve is fitted again, pairing the last two coefficients. This forces the contrast to be zero at t = 1,
and fixes the non-monoticity. The C<fix_hl> flag enables the same behavior at t = 0.
=back
=head3 inv
This method creates an inverted C<ICC::Support::bern> object from an existing object.
B<Usage>
$inv = $bern->inv();
B<Examples>
use ICC::Support::bern;
$bern = ICC::Support::bern->new({'input' => [1, 2, 3], 'output' => [4, 5, 6]});
$bern2 = ICC::Support::bern->new({'input' => [4, 5, 6], 'output' => [1, 2, 3]});
$inv = $bern->inv(); # $inv is identical to $bern2 (see note 1)
=over
=item 1
The inverted object is made by cloning the source object, then swapping the 'input' and 'output' arrays.
=back
=head2 General accessors
=head3 header
This method returns a reference to the header hash (see L<"Object structure"> section). It may also be
used to replace the header hash.
B<Usage>
$hash = $bern->header(); # get header hash reference
$hash = $bern->header($replacement_hash_ref); # set header hash
B<Examples>
use ICC::Support::bern;
$bern = ICC::Support::bern->new({}); # make 'bern' object
$hash = $bern->header(); # get header hash reference
$hash->{'key'} = 'value'; # add key/value to header hash
$bern->header->{'ink'} = 'Cyan'; # set ink color
$ink = $bern->header->{'ink'}; # get ink color
$bern->header({'new' => 'hash'}); # replace header (see note 1)
=over
=item 1
The parameter is copied to the object.
=back
=head3 input
This method returns a reference to the input curve parameters (see L<"Object structure"> section). It may
also be used to replace the parameters.
B<Usage>
$input = $bern->input(); # get input array reference
$input = $bern->input($replacement_array_ref); # set input array
B<Examples>
use ICC::Support::bern;
$bern = ICC::Support::bern->new({'input' => [1, 2, 3]}); # make 'bern' object
$input = $bern->input(); # get input array reference
$c = $bern->input->[0]; # get first input parameter
$bern->input->[0] = 0.1; # set first input parameter
$m = $#{$bern->input}; # get degree of the input curve
$bern->input([4, 5, 6]); # set input array (see note 1)
=over
=item 1
The parameter is copied to the object.
=back
=head3 output
This method returns a reference to the output curve parameters (see L<"Object structure"> section). It may
also be used to replace the parameters.
B<Usage>
$output = $bern->output(); # get output array reference
$output = $bern->output($replacement_array_ref); # set output array
B<Examples>
use ICC::Support::bern;
$bern = ICC::Support::bern->new({'output' => [1, 2, 3]}); # make 'bern' object
$output = $bern->output(); # get output array reference
$c = $bern->output->[0]; # get first output parameter
$bern->output->[0] = 0.1; # set first output parameter
$n = $#{$bern->output}; # get degree of the output curve
$bern->output([4, 5, 6]); # set output array (see note 1)
=over
=item 1
The parameter is copied to the object.
=back
=head2 Transforms
=head3 transform
This method transforms a single input value to a single output value.
B<Usage>
$out = $bern->transform($in);
B<Examples>
use ICC::Support::bern;
$bern = ICC::Support::bern->new({'output' => [0, 0.4, 0.7, 1]}); # make 'bern' object
$bern2 = ICC::Support::bern->new({'input' => [0, 1, -1, 0]}); # non-monotonic 'input' curve (see note 1)
$bern3 = ICC::Support::bern->new({'output' => [0, 1, -1, 0]}); # non-monotonic 'output' curve (see note 1)
$out = $bern->transform(0.5); # input value within the object domain (0 - 1)
$out = $bern->transform(-0.5); # input value outside the domain (see note 2)
$out = $bern->transform(1.5); # input value outside the domain (see note 2)
$out = $bern2->transform(0); # non-monotonic 'input' curve, three possible solutions (see note 3)
$out = $bern2->transform(0.288); # non-monotonic 'input' curve, three possible solutions (see note 4)
$out = $bern2->transform(0.289); # non-monotonic 'input' curve, one solution (see note 4)
$out = $bern3->transform(0.5); # non-monotonic 'output' curve (see note 5)
=over
=item 1
The curve used in these objects is non-monotonic. The curve shape resembles the sin() function.
=item 2
When the input value lies outside the object domain, the output value is extrapolated using the slope at the
nearest endpoint.
=item 3
With this object, the transform is the inverse of the 'input' curve, which is non-monotonic. There are three
possible solutions – 0, 0.5, and 1. The first solution, 0, is returned.
=item 4
There are three possible solutions for an input of 0.288. The first solution, 0.19999999, is returned. But when
the input is increased to 0.289, there is just one solution, 1.096333333. Obviously, this discontinuity may
cause problems.
=item 5
When the 'output' curve is non-monotonic, the output value is uniquely determined, and there are no
discontinuities.
=back
=head3 inverse
This method transforms a single input value to a single output value. It is identical to the B<transform>
method, except the 'input' and 'output' curves are swapped. The inverts the B<transform> function, so the output
is the inverse of the input.
B<Usage>
$out = $bern->inverse($in);
B<Examples>
use ICC::Support::bern;
$bern = ICC::Support::bern->new({'output' => [0, 0.4, 0.7, 1]}); # make 'bern' object
$out = $bern->transform(0.5); # transform some value
$in = $bern->inverse($out); # apply the inverse transform (see note 1)
=over
=item 1
In this example, C<$out = 0.5375>, and C<$in = 0.5>. This is known as a round-trip transform. When you
transform a value, then apply the inverse transform, you should end up with the original value. This may fail
when one of the curves is non-monotonic.
=back
=head3 derivative
This method returns the derivative of the B<transform> method, for a single input value.
B<Usage>
$out = $bern->derivative($in);
B<Examples>
use ICC::Support::bern;
$bern = ICC::Support::bern->new({'output' => [0, 0.4, 0.7, 1]}); # make 'bern' object
$out = $bern->derivative(0.5); # (see note 1)
=over
=item 1
The derivative is calculated using math very similar to the B<transform> method. So, all of the cautions about
non-monotonic B<transform> functions apply.
=back
=head3 parametric
This method returns an array of partial derivatives, ∂out/∂p[i], where p[i] is an 'output' curve parameter.
B<Usage>
@pd = $bern->parametric($in);
B<Examples>
use ICC::Support::bern;
$bern = ICC::Support::bern->new({'output' => [0, 0.4, 0.7, 1]}); # make 'bern' object
@pd = $bern->parametric(0.5);
=head2 Transforms (directional)
These methods use the symmetrical structure of B<ICC::Support::bern> objects to control the direction of the
transform. Aside from that, they're equivalent to the corresponding methods above.
=head3 _transform
This method transforms a single input value to a single output value, with a direction parameter.
If the parameter is 'true', the transform is inverse.
B<Usage>
$out = $bern->_transform($dir, $in);
B<Examples>
use ICC::Support::bern;
$bern = ICC::Support::bern->new({'output' => [0, 0.4, 0.7, 1]}); # make 'bern' object
$out = $bern->_transform(0, $in); # same as 'transform' method
$out = $bern->_transform(1, $in); # same as 'inverse' method
=head3 _derivative
This method returns the derivative of the B<transform> method, for a single input value, if the direction
parameter is 'false'. If the parameter is 'true', the derivative of the B<inverse> method is returned.
B<Usage>
$out = $bern->_derivative($dir, $in);
B<Examples>
use ICC::Support::bern;
$bern = ICC::Support::bern->new({'output' => [0, 0.4, 0.7, 1]}); # make 'bern' object
$out = $bern->_derivative(0, $in); # same as 'derivative' method
$out = $bern->_derivative(1, $in); # derivative of the 'inverse' method
=head3 _parametric
This method returns an array of partial derivatives, ∂out/∂p[i], where p[i] is Bernstein parameter[i].
If the direction parameter is 'false', 'out' is the 'output' array.
If the direction parameter is 'true', 'out' is the 'input' array.
B<Usage>
@pd = $bern->_parametric($dir, $in);
B<Examples>
use ICC::Support::bern;
$bern = ICC::Support::bern->new({'output' => [0, 0.4, 0.7, 1]}); # make 'bern' object
@pd = $bern->_parametric(0, $in); # same as 'parametric' method
@pd = $bern->_parametric(1, $in); # inverse direction, using the 'input' array
=head2 General
=head3 normalize
This method sets the domain and/or range of the object by applying a linear transform to the object's 'input'
and/or 'output' arrays. The default behavior is to adjust both arrays to [0, ... 1] or [1, ... 0], preserving the
current polarity of the curve(s).
B<Usage>
$bern = $bern->normalize();
$bern = $bern->normalize($hash_ref);
B<Examples>
use ICC::Support::bern;
$bern = ICC::Support::bern->new({'output' => [0, 30, 57.5, 82, 100]}); # make 'bern' object
$bern2 = ICC::Support::bern->new({'input' => [0, 100], 'output' => [0, 30, 57.5, 82, 100]});
$bern3 = ICC::Support::bern->new({'input' => [100, 0], 'output' => [0, 30, 57.5, 82, 100]});
$bern4 = ICC::Support::bern->new({'output' => [0.2, -3, 3, 0.6]}); # non-monotonic curve
$bern->normalize(); # 'output' curve normalized, is now [0, 0.3, 0.575, 0.82, 1] (see note 1)
$bern2->normalize(); # both 'input' and 'output' curves normalized, 'input' is now [0, 1]
$bern3->normalize(); # curve polarity is preserved, 'input' is now [1, 0]
$bern2->normalize({'output' => [0, 100]}); # 'output' curve is now [0, 30, 57.5, 82, 100] (see notes 2 and 3)
$bern2->normalize({'output' => [100, 0]}); # 'output' curve is inverted [100, 70, 42.5, 18, 0]
$bern4->normalize({'output' => ['endpoints' => 0, 1]}); # curve endpoints transformed to [0, 1] (see note 4)
$bern4->normalize({'output' => ['minmax' => 0, 1]}); # curve minimum/maximum transformed to [0, 1]
$bern4->normalize({'output' => [0, 1 => 4, 5]}); # curve transformed so that 0 => 4 and 1 => 5
$cvst->array->[1] = $bern->normalize(); # returns object reference (see note 5)
=over
=item 1
This object has no C<'input'> curve, so the curve domain is [0 - 1].
=item 2
Array values are linearly transformed, y = mx + b. The constants m and b are determined from the x-y values
of two points. The hash specifies these x-y values in various ways.
=item 3
Hash keys are C<'input'>, and C<'output'>.
Hash values for C<'input'> and C<'output'> are an array reference.
The array may have 2, 3, or 4 elements.
The last 2 elements are the y-values of two points for the linear transform.
If there are 3 array elements, the first element is either C<'endpoints'> or C<'minmax'>.
If there are 4 array elements, the first two elements are the transform x-values.
If there are 2 array elements, or C<'endpoints'> is specified, the x-values are the first and last array values.
If C<'minmax'> is specified, the x-values are the minimum and maximum curve values. These values might not
be the endpoints.
=item 4
In Perl, 'C<=E<gt>>' is the same as a comma, so C<['endpoints' =E<gt> 0, 1]> is the same as C<['endpoints', 0, 1]>.
=item 5
The object reference is returned to support "pass-through" method calls.
=back
=head3 monotonic
This method returns a combined list of min/max values for the 'input' and 'output' curves. By default, t-values are
returned. These values are converted to 'input' or 'output' values by adding the optional format parameter.
If the list is empty, there are no min/max values, and the curve is monotonic.
B<Usage>
@minmax = $bern->monotonic();
@minmax = $bern->monotonic($format);
B<Examples>
use ICC::Support::bern;
$bern = ICC::Support::bern->new({'input' => [-1, 3, 1.5], 'output' => [0.5, -5, 2, 0.9]}); # non-monotonic curves
@minmax = $bern->monotonic(); # returns t-values (0 - 1)
@minmax = $bern->monotonic('input'); # returns 'input' values
@minmax = $bern->monotonic('output'); # returns 'output' values
print "curve is non-monotonic\n" if (@minmax);
=head3 update
This method updates the internal elements of the object. It should be called when the contents of the curve arrays
are changed externally, e.g. curve optimization. For C<ICC::Support::bern> objects, this method is the same as
the '_minmax' function.
B<Usage>
$bern->update();
=head3 table
This method makes a lookup table (LUT) for use in certain profile objects ('curv', 'mft1', 'mft2').
It is assumed that the domain and range of the 'bern' object is [0, 1].
B<Usage>
$table = $bern->table($n);
$table = $bern->table($n, $dir);
B<Examples>
use ICC::Support::bern;
$bern = ICC::Support::bern->new({'output' => [0, 30, 57.5, 82, 100]}); # make 'bern' object
$bern->normalize(); # normalize the object
$table = $bern->table(1285); # make a table with 1285 steps
$table = $bern->table(1285, 1); # make an inverse table with 1285 steps
=head3 curv
This method makes an equivalent 'curv' object.
It is assumed that the domain and range of the 'bern' object is [0, 1].
B<Usage>
$curv = $bern->curv($n);
$curv = $bern->curv($n, $dir);
B<Examples>
use ICC::Support::bern;
$bern = ICC::Support::bern->new({'output' => [0, 30, 57.5, 82, 100]}); # make 'bern' object
$bern->normalize(); # normalize the object
$curv = $bern->curv(1285); # make a 'curv' object with 1285 steps
$curv = $bern->curv(1285, 1); # make an inverse 'curv' object with 1285 steps
=head3 dump, sdump
This method returns a string showing the structure of the 'bern' object.
B<Usage>
$string = $bern->sdump([$format]);
$bern->dump([$format]);
B<Examples>
use ICC::Support::bern;
$bern = ICC::Support::bern->new({'input' => [0, 100], 'output' => 4});
$string = $bern->sdump(); # dump to string
$bern->dump(); # dump to STDOUT
=head1 SEE ALSO
=head2 Math References
Farouki, Rida T. "The Bernstein polynomial basis: a centennial retrospective." L<http://mae.engr.ucdavis.edu/~farouki/bernstein.pdf>
Joy, Kenneth I. "Bernstein Polynomials." CAGD Notes. L<http://www.idav.ucdavis.edu/education/CAGDNotes/Bernstein-Polynomials.pdf>
Weisstein, Eric W. "Bernstein Polynomial." MathWorld. L<http://mathworld.wolfram.com/BernsteinPolynomial.html>
Wolfram Notebook L<http://mathworld.wolfram.com/notebooks/SpecialFunctions/BernsteinPolynomial.nb>
=head1 LICENSE
Programs in this distribution, authored by William B. Birkett, are licensed under the GNU General Public License, v3.
See L<https://www.gnu.org/licenses/gpl-3.0.html> for license details.
=head1 AUTHOR
William B. Birkett, E<lt>wbirkett@doplganger.comE<gt>
=head1 COPYRIGHT
Copyright © 2004-2020 by William B. Birkett
=cut