ICC::Support::bern - An object oriented module for modeling transfer functions (curves) using Bernstein polynomials.
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]);
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 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.
An 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.
An 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.
This method creates an ICC::Support::bern
object.
With no parameters, the object contains the empty basic structure (see "Object structure").
An object is normally created from a hash, as illustrated below.
Usage
$bern = ICC::Support::bern->new(); # empty object
$bern = ICC::Support::bern->new($hash); # from a hash
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)
$bern = ICC::Support::bern->new({'output' => [(undef) x 4], 'fit' => [$x, $y], 'mono' => 0}); # fit curve to data (see note 11)
An empty hash ({}
) makes an object with the input and output curves disabled. This object will transform values unchanged (identity transform).
Hash keys are 'input'
, 'output'
, 'fit'
, 'fix_hl'
, 'fix_sh'
, and 'mono'
Hash values for 'input'
and 'output'
are either an integer, or a vector.
Hash value for 'fit'
is a reference to an array containing a set of points as x and y vectors.
Hash values for 'fix_hl'
and 'fix_sh'
are boolean (0 or 1).
Hash values for 'mono'
are boolean (0 or 1).
Setting the coefficient array to []
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.
Setting the coefficient array to [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.
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.
This example makes an object with a degree 3 '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.
This example makes an object with a degree 4 'output' curve. The Bernstein coefficients are set to the vector [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.
This example make an object with a degree 3 '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 '(undef) x 4
', which results in 4 undefined 'output' coefficients, prior to the fitting process.
This example is the same the previous one, except the first 'output'
coefficient is set to 0, followed by 3 undefined coefficients. Only these coefficients are fitted; the first one remains fixed at 0.
This example fits the parameters to the x-y data with the fix_sh
flag set. If the resulting 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 fix_hl
flag enables the same behavior at t = 0.
This example shows the 'mono' hash value set to 0, which suppresses the 'not monotonic' warning. In some cases, non-monotonic curves are perfectly fine, and a warning is inappropriate.
This method creates an inverted ICC::Support::bern
object from an existing object.
Usage
$inv = $bern->inv();
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)
The inverted object is made by cloning the source object, then swapping the 'input' and 'output' arrays.
This method returns a reference to the header hash (see "Object structure" section). It may also be used to replace the header hash.
Usage
$hash = $bern->header(); # get header hash reference
$hash = $bern->header($replacement_hash_ref); # set header hash
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)
The parameter is copied to the object.
This method returns a reference to the input curve parameters (see "Object structure" section). It may also be used to replace the parameters.
Usage
$input = $bern->input(); # get input array reference
$input = $bern->input($replacement_array_ref); # set input array
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)
The parameter is copied to the object.
This method returns a reference to the output curve parameters (see "Object structure" section). It may also be used to replace the parameters.
Usage
$output = $bern->output(); # get output array reference
$output = $bern->output($replacement_array_ref); # set output array
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)
The parameter is copied to the object.
This method transforms a single input value to a single output value.
Usage
$out = $bern->transform($in);
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)
The curve used in these objects is non-monotonic. The curve shape resembles the sin() function.
When the input value lies outside the object domain, the output value is extrapolated using the slope at the nearest endpoint.
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.
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.
When the 'output' curve is non-monotonic, the output value is uniquely determined, and there are no discontinuities.
This method transforms a single input value to a single output value. It is identical to the transform method, except the 'input' and 'output' curves are swapped. That inverts the transform function, so the output is the inverse of the input.
Usage
$out = $bern->inverse($in);
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)
In this example, $out = 0.5375
, and $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.
This method returns the derivative of the transform method, for a single input value.
Usage
$out = $bern->derivative($in);
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)
The derivative is calculated using math very similar to the transform method. So, all of the cautions about non-monotonic transform functions apply.
This method returns an array of partial derivatives, ∂out/∂p[i], where p[i] is an 'output' curve parameter.
Usage
@pd = $bern->parametric($in);
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);
These methods use the symmetrical structure of ICC::Support::bern objects to control the direction of the transform. Aside from that, they're equivalent to the corresponding methods above.
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.
Usage
$out = $bern->_transform($dir, $in);
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
This method returns the derivative of the transform method, for a single input value, if the direction parameter is 'false'. If the parameter is 'true', the derivative of the inverse method is returned.
Usage
$out = $bern->_derivative($dir, $in);
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
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.
Usage
@pd = $bern->_parametric($dir, $in);
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
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).
Usage
$bern = $bern->normalize();
$bern = $bern->normalize($hash_ref);
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)
This object has no 'input'
curve, so the curve domain is [0 - 1].
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.
Hash keys are 'input'
, and 'output'
.
Hash values for 'input'
and '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 'endpoints'
or 'minmax'
. If there are 4 array elements, the first two elements are the transform x-values.
If there are 2 array elements, or 'endpoints'
is specified, the x-values are the first and last array values.
If 'minmax'
is specified, the x-values are the minimum and maximum curve values. These values might not be the endpoints.
In Perl, '=>
' is the same as a comma, so ['endpoints' => 0, 1]
is the same as ['endpoints', 0, 1]
.
The object reference is returned to support "pass-through" method calls.
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.
Usage
@minmax = $bern->monotonic();
@minmax = $bern->monotonic($format);
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);
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 ICC::Support::bern
objects, this method is the same as the '_minmax' function.
Usage
$bern->update();
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].
Usage
$table = $bern->table($n);
$table = $bern->table($n, $dir);
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
This method makes an equivalent 'curv' object. It is assumed that the domain and range of the 'bern' object is [0, 1].
Usage
$curv = $bern->curv($n);
$curv = $bern->curv($n, $dir);
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
This method returns a string showing the structure of the 'bern' object.
Usage
$string = $bern->sdump([$format]);
$bern->dump([$format]);
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
Farouki, Rida T. "The Bernstein polynomial basis: a centennial retrospective." http://mae.engr.ucdavis.edu/~farouki/bernstein.pdf
Joy, Kenneth I. "Bernstein Polynomials." CAGD Notes. http://www.idav.ucdavis.edu/education/CAGDNotes/Bernstein-Polynomials.pdf
Weisstein, Eric W. "Bernstein Polynomial." MathWorld. http://mathworld.wolfram.com/BernsteinPolynomial.html
Wolfram Notebook http://mathworld.wolfram.com/notebooks/SpecialFunctions/BernsteinPolynomial.nb
Programs in this distribution, authored by William B. Birkett, are licensed under the GNU General Public License, v3.
See https://www.gnu.org/licenses/gpl-3.0.html for license details.
William B. Birkett, <wbirkett@doplganger.com>
Copyright © 2004-2024 by William B. Birkett