File Coverage

lib/ICC/Support/ratfunc.pm
Criterion Covered Total %
statement 21 216 9.7
branch 1 120 0.8
condition 0 60 0.0
subroutine 6 20 30.0
pod 1 8 12.5
total 29 424 6.8


line stmt bran cond sub pod time code
1             package ICC::Support::ratfunc;
2              
3 2     2   100000 use strict;
  2         15  
  2         57  
4 2     2   10 use Carp;
  2         3  
  2         135  
5              
6             our $VERSION = 0.10;
7              
8             # revised 2016-10-26
9             #
10             # Copyright © 2004-2019 by William B. Birkett
11              
12             # add development directory
13 2     2   499 use lib 'lib';
  2         679  
  2         14  
14              
15             # inherit from Shared
16 2     2   682 use parent qw(ICC::Shared);
  2         296  
  2         13  
17              
18             # enable static variables
19 2     2   125 use feature 'state';
  2         3  
  2         5880  
20              
21             =encoding utf-8
22              
23             This module implements a simple rational function ('ratfunc') transform for 3-channel data.
24              
25             The transform is explained in the document 'rational_function_color_transform.txt'.
26              
27             The primary application is converting RGB camera/scanner data to XYZ.
28              
29             We often use a 3x4 matrix to do this,
30              
31             | a11, a12, a13, a14 |
32             | a21, a22, a23, a24 |
33             | a31, a32, a33, a34 |
34              
35             To use this matrix, we add a column containing '1' to the input data,
36              
37             [R, G, B] => [R, G, B, 1]
38              
39             Then we use matrix multiplication to compute the XYZ values from these augmented RGB values.
40              
41             [X, Y, Z] = [3x4 matrix] x [R, G, B, 1]
42              
43             If the camera or scanner has RGB spectral sensitivities derived from color matching functions (Luther-Ives condition), the accuracy
44             of this simple transform will be excellent. However, the spectral sensitivity curves are not always optimal.
45              
46             We may be able to achieve slightly better results using rational functions. A rational function is the ratio of two polynomial
47             functions. We use extremely simple, linear functions of RGB. We extend the 3x4 matrix by adding three rows to get a 6x4 matrix,
48              
49             | a11, a12, a13, a14 |
50             | a21, a22, a23, a24 |
51             | a31, a32, a33, a34 |
52             | a41, a42, a43, 1 |
53             | a51, a52, a53, 1 |
54             | a61, a62, a63, 1 |
55              
56             Now, when we multiply by the augmented RGB matrix, we get,
57              
58             [Xn, Yn, Zn, Xd, Yd, Zd] = [6x4 matrix] x [R, G, B, 1]
59              
60             Then we reduce these values to ratios,
61              
62             [X, Y, Z] = [Xn/Xd, Yn/Yd, Zn/Zd]
63              
64             If the added coefficients, a41, a42, ... a63, are all zero, the denominators will all be 1, and the transform is the same as the 3x3
65             matrix with offsets. If these coefficients are non-zero, the X, Y, Z functions will be non-linear, which may improve the accuracy of
66             the transform.
67              
68             The advantage of this transform is that it provides some additional degrees of freedom compared to the 3x3 matrix. This allows us to
69             'fix' some points to improve the reproduction of a particular original. The transform may have some curvature, but it is smooth and
70             gradual, so congruence is maintained. This transform cannot improve the color quality of the sensor, but it can be used to fine tune
71             images.
72              
73             The object's matrix is compatible with the XS function 'ICC::Support::Image::ratfunc_transform_float'. The intention is to optimize
74             the matrix using the 'ratfunc.pm' object, then transform images using the XS function.
75              
76             The size of the object's matrix is always 6x4. If we attempt to make a larger matrix, an error occurs. If we supply a smaller matrix,
77             the missing coefficients are those of the identity matrix. The identity matrix looks like this,
78              
79             | 1, 0, 0, 0 |
80             | 0, 1, 0, 0 |
81             | 0, 0, 1, 0 |
82             | 0, 0, 0, 1 |
83             | 0, 0, 0, 1 |
84             | 0, 0, 0, 1 |
85              
86             For example, a 3x3 matrix will be copied to the first three rows and columns of the above identity matrix. In that case, the 'ratfunc'
87             transform will be the same as the 'matf' transform (straight matrix multiplication).
88              
89             =cut
90              
91             # create new ratfunc object
92             # returns an empty object with no parameters
93             # hash keys are: ('header', 'matrix', 'offset')
94             # 'header' value is a hash reference
95             # 'matrix' value is a 2D array reference -or- Math::Matrix object
96             # returns identity object with an empty hash ({})
97             # when the parameters are input and output arrays, the 'fit' method is called on the object
98             # parameter: ()
99             # parameter: ({})
100             # parameter: (ref_to_attribute_hash)
101             # parameter: (matf_object)
102             # parameters: (ref_to_input_array, ref_to_output_array)
103             # returns: (ref_to_object)
104             sub new {
105              
106             # get object class
107 1     1 0 835 my $class = shift();
108              
109             # create empty ratfunc object
110 1         4 my $self = [
111             {}, # header
112             [], # matrix
113             ];
114              
115             # local parameter
116 1         2 my ($info);
117              
118             # if there are parameters
119 1 50       4 if (@_) {
120            
121             # if one parameter, a hash reference
122 0 0 0     0 if (@_ == 1 && ref($_[0]) eq 'HASH') {
    0 0        
    0 0        
123            
124             # make new ratfunc object from attribute hash
125 0         0 _new_from_hash($self, shift());
126            
127             # if one parameter, a 'matf' object
128             } elsif (@_ == 1 && UNIVERSAL::isa(ref($_[0]), 'ICC::Profile::matf')) {
129            
130             # make new ratfunc object from 'matf' object
131 0         0 _new_from_matf($self, shift());
132            
133             # if two or three parameters
134             } elsif (@_ == 2 || @_ == 3) {
135            
136             # fit the object to data
137 0 0       0 ($info = fit($self, @_)) && croak("\'fit\' routine failed with error $info");
138            
139             } else {
140            
141             # error
142 0         0 croak('\'ratfunc\' invalid parameter(s)');
143            
144             }
145            
146             }
147              
148             # bless object
149 1         3 bless($self, $class);
150              
151             # return object reference
152 1         3 return($self);
153              
154             }
155              
156             # get/set reference to header hash
157             # parameters: ([ref_to_new_hash])
158             # returns: (ref_to_hash)
159             sub header {
160              
161             # get object reference
162 0     0 0   my $self = shift();
163              
164             # if there are parameters
165 0 0         if (@_) {
166            
167             # if one parameter, a hash reference
168 0 0 0       if (@_ == 1 && ref($_[0]) eq 'HASH') {
169            
170             # set header to new hash
171 0           $self->[0] = {%{shift()}};
  0            
172            
173             } else {
174            
175             # error
176 0           croak('\'header\' attribute must be a hash reference');
177            
178             }
179            
180             }
181              
182             # return reference
183 0           return($self->[0]);
184              
185             }
186              
187             # get/set reference to matrix array
188             # parameters: ([ref_to_new_array])
189             # returns: (ref_to_array)
190             sub matrix {
191              
192             # get object reference
193 0     0 0   my $self = shift();
194              
195             # if there are parameters
196 0 0         if (@_) {
197            
198             # if one parameter, a reference to a 2-D array -or- Math::Matrix object
199 0 0 0       if (@_ == 1 && ((ref($_[0]) eq 'ARRAY' && @{$_[0]} == grep {ref() eq 'ARRAY'} @{$_[0]}) || UNIVERSAL::isa($_[0], 'Math::Matrix'))) {
      0        
200            
201             # verify number of rows
202 0 0         ($#{$_[0]} < 6) or croak('\'matrix\' array has more than 6 rows');
  0            
203            
204             # make identity matrix (6x4)
205 0           $self->[1] = bless([
206             [1, 0, 0, 0],
207             [0, 1, 0, 0],
208             [0, 0, 1, 0],
209             [0, 0, 0, 1],
210             [0, 0, 0, 1],
211             [0, 0, 0, 1],
212             ], 'Math::Matrix');
213            
214             # for each row
215 0           for my $i (0 .. $#{$_[0]}) {
  0            
216            
217             # verify number of columns
218 0 0         ($#{$_[0]->[$i]} < 4) or croak('\'matrix\' array has more than 4 columns');
  0            
219            
220             # for each column
221 0           for my $j (0 .. $#{$_[0]->[$i]}) {
  0            
222            
223             # verify matrix element is a number
224 0 0         (Scalar::Util::looks_like_number($_[0]->[$i][$j])) or croak('\'matrix\' element not numeric');
225            
226             # copy matrix element
227 0           $self->[1][$i][$j] = $_[0]->[$i][$j];
228            
229             }
230            
231             }
232            
233             } else {
234            
235             # error
236 0           croak('\'matrix\' attribute must be a 2-D array reference or Math::Matrix object');
237            
238             }
239            
240             }
241              
242             # return object reference
243 0           return($self->[1]);
244              
245             }
246              
247             # fit ratfunc object to data
248             # uses LAPACK dgels function to perform a least-squares fit
249             # fitting is done with or without offset, according to offset_flag
250             # input and output are 2D array references -or- Math::Matrix objects
251             # parameters: (ref_to_input_array, ref_to_output_array, [offset_flag])
252             # returns: (dgels_info_value)
253             sub fit {
254              
255             # get parameters
256 0     0 0   my ($self, $in, $out, $oflag) = @_;
257              
258             # local variables
259 0           my ($info, $ab);
260              
261             # check if ICC::Support::Lapack module is loaded
262 0           state $lapack = defined($INC{'ICC/Support/Lapack.pm'});
263              
264             # verify ICC::Support::Lapack module is loaded
265 0 0         ($lapack) or croak('\'fit\' method requires ICC::Support::Lapack module');
266              
267             # resolve offset flag
268 0 0         $oflag = 0 if (! defined($oflag));
269              
270             # verify input array
271 0 0 0       (ref($in) eq 'ARRAY' && ref($in->[0]) eq 'ARRAY' && ! ref($in->[0][0])) || UNIVERSAL::isa($in, 'Math::Matrix') or croak('fit input not a 2-D array reference');
      0        
      0        
272              
273             # verify output array
274 0 0 0       (ref($out) eq 'ARRAY' && ref($out->[0]) eq 'ARRAY' && ! ref($out->[0][0])) || UNIVERSAL::isa($out, 'Math::Matrix') or croak('fit output not a 2-D array reference');
      0        
      0        
275              
276             # verify array dimensions
277 0 0         ($#{$in} == $#{$out}) or croak('input and output arrays have different number of rows');
  0            
  0            
278 0 0         ($#{$in->[0]} == 2) or croak('input samples must have 3 elements');
  0            
279 0 0         ($#{$out->[0]} == 2) or croak('output samples must have 3 elements');
  0            
280              
281             # fit the matrix
282 0           ($info, $ab) = ICC::Support::Lapack::matf_fit($in, $out, $oflag);
283              
284             # check result
285 0 0         carp('fit failed - bad parameter when calling dgels') if ($info < 0);
286 0 0         carp('fit failed - A matrix not full rank') if ($info > 0);
287              
288             # initialize matrix object
289 0           $self->[1] = bless([], 'Math::Matrix');
290              
291             # for each row
292 0           for my $i (0 .. 2) {
293            
294             # for each column
295 0           for my $j (0 .. 2) {
296            
297             # set matrix element (transposing)
298 0           $self->[1][$i][$j] = $ab->[$j][$i];
299            
300             }
301            
302             # set offset value
303 0 0         $self->[1][$i][3] = $oflag ? $ab->[3][$i] : 0;
304            
305             # set divisor row
306 0           $self->[1][$i + 3] = [0, 0, 0, 1];
307            
308             }
309              
310             # return info value
311 0           return($info);
312              
313             }
314              
315             # transform data
316             # supported input types:
317             # parameters: (list, [hash])
318             # parameters: (vector, [hash])
319             # parameters: (matrix, [hash])
320             # parameters: (Math::Matrix_object, [hash])
321             # parameters: (structure, [hash])
322             # returns: (same_type_as_input)
323             sub transform {
324              
325             # set hash value (0 or 1)
326 0 0   0 0   my $h = ref($_[-1]) eq 'HASH' ? 1 : 0;
327              
328             # if input a 'Math::Matrix' object
329 0 0 0       if (@_ == $h + 2 && UNIVERSAL::isa($_[1], 'Math::Matrix')) {
    0 0        
    0          
330            
331             # call matrix transform
332 0           &_trans2;
333            
334             # if input an array reference
335             } elsif (@_ == $h + 2 && ref($_[1]) eq 'ARRAY') {
336            
337             # if array contains numbers (vector)
338 0 0 0       if (! ref($_[1][0]) && @{$_[1]} == grep {Scalar::Util::looks_like_number($_)} @{$_[1]}) {
  0 0 0        
  0            
  0            
339            
340             # call vector transform
341 0           &_trans1;
342            
343             # if array contains vectors (2-D array)
344 0 0         } elsif (ref($_[1][0]) eq 'ARRAY' && @{$_[1]} == grep {ref($_) eq 'ARRAY' && Scalar::Util::looks_like_number($_->[0])} @{$_[1]}) {
  0            
  0            
345            
346             # call matrix transform
347 0           &_trans2;
348            
349             } else {
350            
351             # call structure transform
352 0           &_trans3;
353            
354             }
355            
356             # if input a list (of numbers)
357 0           } elsif (@_ == $h + 1 + grep {Scalar::Util::looks_like_number($_)} @_) {
358            
359             # call list transform
360 0           &_trans0;
361            
362             } else {
363            
364             # error
365 0           croak('invalid transform input');
366            
367             }
368            
369             }
370              
371             =cut
372              
373             # inverse transform
374             # note: number of undefined output values must equal number of defined input values
375             # note: input array contains the final calculated input values upon return
376             # parameters: (ref_to_input_array, ref_to_output_array)
377             sub inverse {
378              
379             # get parameters
380             my ($self, $in, $out) = @_;
381              
382             # local variables
383             my ($i, $j, @si, @so);
384             my ($int, $info, $delta, $sys, $res, $mat);
385              
386             # check if ICC::Support::Lapack module is loaded
387             state $lapack = defined($INC{'ICC/Support/Lapack.pm'});
388              
389             # initialize indices
390             $i = $j = -1;
391              
392             # build slice arrays while validating input and output arrays
393             ((grep {$i++; defined() && push(@si, $i)} @{$in}) == (grep {$j++; ! defined() && push(@so, $j)} @{$out})) or croak('wrong number of undefined values');
394              
395             # for each undefined output value
396             for my $i (@so) {
397            
398             # set to 0
399             $out->[$i] = 0;
400            
401             }
402              
403             # if ICC::Support::Lapack module is loaded
404             if ($lapack) {
405            
406             # compute initial transform values
407             $int = ICC::Support::Lapack::matf_vec_trans($out, $self->[1]);
408            
409             # for each input
410             for my $i (0 .. $#si) {
411            
412             # for each output
413             for my $j (0 .. $#so) {
414            
415             # copy Jacobian value to system matrix
416             $sys->[$i][$j] = $self->[1][$si[$i]][$so[$j]];
417            
418             }
419            
420             # compute residual value
421             $res->[$i][0] = $in->[$si[$i]] - $int->[$si[$i]];
422            
423             }
424            
425             # solve for delta values
426             ($info, $delta) = ICC::Support::Lapack::solve($sys, $res);
427            
428             # report linear system error
429             ($info) && print "ratfunc inverse error $info: @{$in}\n";
430            
431             # for each output value
432             for my $i (0 .. $#so) {
433            
434             # add delta value
435             $out->[$so[$i]] += $delta->[$i][0];
436            
437             }
438            
439             # compute final transform values
440             @{$in} = @{ICC::Support::Lapack::matf_vec_trans($out, $self->[1])};
441            
442             } else {
443            
444             # compute initial transform values
445             $int = [_trans0($self, @{$out})];
446            
447             # for each input
448             for my $i (0 .. $#si) {
449            
450             # for each output
451             for my $j (0 .. $#so) {
452            
453             # copy Jacobian value to solution matrix
454             $mat->[$i][$j] = $self->[1][$si[$i]][$so[$j]];
455            
456             }
457            
458             # save residual value to solution matrix
459             $mat->[$i][$#si + 1] = $in->[$si[$i]] - $int->[$si[$i]];
460            
461             }
462            
463             # bless Matrix
464             bless($mat, 'Math::Matrix');
465            
466             # solve for delta values
467             $delta = $mat->solve || print "ratfunc inverse error: @{$in}\n";
468            
469             # for each output value
470             for my $i (0 .. $#so) {
471            
472             # add delta value
473             $out->[$so[$i]] += $delta->[$i][0];
474            
475             }
476            
477             # compute final transform values
478             @{$in} = _trans0($self, @{$out});
479            
480             }
481            
482             }
483              
484             # compute Jacobian matrix
485             # note: input values only required for output values
486             # parameters: ([input_vector])
487             # returns: (ref_to_Jacobian_matrix, [output_vector])
488             sub jacobian {
489              
490             # get object reference
491             my $self = shift();
492              
493             # if output values wanted
494             if (wantarray) {
495            
496             # return Jacobian and output values
497             return(bless(Storable::dclone($self->[1]), 'Math::Matrix'), _trans1($self, $_[0]));
498            
499             } else {
500            
501             # return Jacobian only
502             return(bless(Storable::dclone($self->[1]), 'Math::Matrix'));
503            
504             }
505            
506             }
507              
508             =cut
509              
510             # get number of input channels
511             # returns: (number)
512             sub cin {
513              
514             # get object reference
515 0     0 0   my $self = shift();
516              
517             # return
518 0           return(3);
519              
520             }
521              
522             # get number of output channels
523             # returns: (number)
524             sub cout {
525              
526             # get object reference
527 0     0 0   my $self = shift();
528              
529             # return
530 0           return(3);
531              
532             }
533              
534             # print object contents to string
535             # format is an array structure
536             # parameter: ([format])
537             # returns: (string)
538             sub sdump {
539              
540             # get parameters
541 0     0 1   my ($self, $p) = @_;
542              
543             # local variables
544 0           my ($fmt, $s, $rows, $fn);
545              
546             # resolve parameter to an array reference
547 0 0         $p = defined($p) ? ref($p) eq 'ARRAY' ? $p : [$p] : [];
    0          
548              
549             # get format string
550 0 0 0       $fmt = defined($p->[0]) && ! ref($p->[0]) ? $p->[0] : 'm';
551              
552             # set string to object ID
553 0           $s = sprintf("'%s' object, (0x%x)\n", ref($self), $self);
554              
555             # get matrix rows
556 0           $rows = $#{$self->[1]};
  0            
557              
558             # if empty object
559 0 0         if ($rows < 0) {
560            
561             # append string
562 0           $s .= "\n";
563            
564             } else {
565            
566             # append string
567 0           $s .= "matrix values\n";
568            
569             # for each row
570 0           for my $i (0 .. $rows) {
571            
572             # make number format
573 0           $fn = ' %10.5f' x @{$self->[1][$i]};
  0            
574            
575             # append matrix row
576 0           $s .= sprintf("$fn\n", @{$self->[1][$i]});
  0            
577            
578             }
579            
580             }
581              
582             # return string
583 0           return($s);
584              
585             }
586              
587             # recursive transform
588             # array structure is traversed until scalar arrays are found and transformed
589             # parameters: (ref_to_object, subroutine_reference, input_array_reference, output_array_reference)
590             sub _crawl {
591              
592             # get parameters
593 0     0     my ($self, $sub, $in, $out) = @_;
594              
595             # if input is a vector (reference to a numeric array)
596 0 0         if (@{$in} == grep {Scalar::Util::looks_like_number($_)} @{$in}) {
  0            
  0            
  0            
597            
598             # transform input vector and copy to output
599 0           @{$out} = @{$sub->($self, $in)};
  0            
  0            
600            
601             } else {
602            
603             # for each input element
604 0           for my $i (0 .. $#{$in}) {
  0            
605            
606             # if an array reference
607 0 0         if (ref($in->[$i]) eq 'ARRAY') {
608            
609             # transform next level
610 0           _crawl($self, $sub, $in->[$i], $out->[$i] = []);
611            
612             } else {
613            
614             # error
615 0           croak('invalid input structure');
616            
617             }
618            
619             }
620            
621             }
622            
623             }
624              
625             # transform list
626             # parameters: (object_reference, list, [hash])
627             # returns: (list)
628             sub _trans0 {
629              
630             # local variables
631 0     0     my ($self, $hash, @out, $den);
632              
633             # get object reference
634 0           $self = shift();
635              
636             # get optional hash
637 0 0         $hash = pop() if (ref($_[-1]) eq 'HASH');
638              
639             # validate number of input channels
640 0 0         (@_ == 3) or croak('input samples must have 3 channels');
641              
642             # augment input sample
643 0           push(@_, 1);
644              
645             # for each output
646 0           for my $i (0 .. 2) {
647            
648             # compute denominator
649 0           $den = ICC::Shared::dotProduct(\@_, $self->[1][$i + 3]);
650            
651             # add matrix value
652 0 0         $out[$i] = ($den == 0) ? 'inf' : ICC::Shared::dotProduct(\@_, $self->[1][$i])/$den;
653            
654             }
655              
656             # return output data
657 0           return(@out);
658              
659             }
660              
661             # transform vector
662             # parameters: (object_reference, vector, [hash])
663             # returns: (vector)
664             sub _trans1 {
665              
666             # get parameters
667 0     0     my ($self, $in, $hash) = @_;
668              
669             # check if ICC::Support::Lapack module is loaded
670 0           state $lapack = defined($INC{'ICC/Support/Lapack.pm'});
671              
672             # validate number of input channels
673 0 0         (@{$in} == 3) or croak('input samples must have 3 channels');
  0            
674              
675             # if ICC::Support::Lapack module is loaded
676 0 0         if ($lapack) {
677            
678             # compute output vector using BLAS dgemv function
679 0           return(ICC::Support::Lapack::ratfunc_vec_trans($in, $self->[1]));
680            
681             } else {
682            
683             # return
684 0           return([_trans0($self, @{$in})]);
  0            
685            
686             }
687              
688             }
689              
690             # transform matrix (2-D array -or- Math::Matrix object)
691             # parameters: (object_reference, matrix, [hash])
692             # returns: (matrix)
693             sub _trans2 {
694              
695             # get parameters
696 0     0     my ($self, $in, $hash) = @_;
697              
698             # local variables
699 0           my ($info, $out, $aug, $den);
700              
701             # check if ICC::Support::Lapack module is loaded
702 0           state $lapack = defined($INC{'ICC/Support/Lapack.pm'});
703              
704             # validate number of input channels
705 0 0         (@{$in->[0]} == 3) or croak('input samples must have 3 channels');
  0            
706              
707             # if ICC::Support::Lapack module is loaded
708 0 0         if ($lapack) {
709            
710             # compute output matrix using BLAS dgemm function
711 0           $out = ICC::Support::Lapack::ratfunc_mat_trans($in, $self->[1]);
712            
713             } else {
714            
715             # for each row
716 0           for my $i (0 .. $#{$in}) {
  0            
717            
718             # augment input sample
719 0           $aug = [@{$in->[$i]}, 1];
  0            
720            
721             # for each column
722 0           for my $j (0 .. 2) {
723            
724             # compute denominator
725 0           $den = ICC::Shared::dotProduct($aug, $self->[1][$j + 3]);
726            
727             # add dot product
728 0 0         $out->[$i][$j] = ($den == 0) ? 'inf' : ICC::Shared::dotProduct($aug, $self->[1][$j])/$den;
729            
730             }
731            
732             }
733            
734             }
735              
736             # return output matrix (Math::Matrix object or 2-D array)
737 0 0         return(UNIVERSAL::isa($in, 'Math::Matrix') ? bless($out, 'Math::Matrix') : $out);
738              
739             }
740              
741             # transform structure
742             # parameters: (object_reference, structure, [hash])
743             # returns: (structure)
744             sub _trans3 {
745              
746             # get parameters
747 0     0     my ($self, $in, $hash) = @_;
748              
749             # transform the array structure
750 0           _crawl($self, \&_trans1, $in, my $out = []);
751              
752             # return output structure
753 0           return($out);
754              
755             }
756              
757             # make new ratfunc object from matf object
758             # parameters: (ref_to_object, matf_object)
759             sub _new_from_matf {
760              
761             # get parameters
762 0     0     my ($self, $matf) = @_;
763              
764             # local variables
765 0           my ($value);
766              
767             # make identity matrix (6x4)
768 0           $self->[1] = bless([
769             [1, 0, 0, 0],
770             [0, 1, 0, 0],
771             [0, 0, 1, 0],
772             [0, 0, 0, 1],
773             [0, 0, 0, 1],
774             [0, 0, 0, 1],
775             ], 'Math::Matrix');
776              
777             # get 'matf' matrix
778 0           $value = $matf->matrix;
779              
780             # verify number of rows
781 0 0         ($#{$value} < 6) or croak('\'matf\' matrix has more than 6 rows');
  0            
782              
783             # for each row
784 0           for my $i (0 .. $#{$value}) {
  0            
785            
786             # verify number of columns
787 0 0         ($#{$value->[$i]} < 3) or croak('\'matf\' matrix has more than 3 columns');
  0            
788            
789             # for each column
790 0           for my $j (0 .. $#{$value->[$i]}) {
  0            
791            
792             # verify matrix element is a number
793 0 0         (Scalar::Util::looks_like_number($value->[$i][$j])) or croak('\'matf\' matrix element not numeric');
794            
795             # copy matrix element
796 0           $self->[1][$i][$j] = $value->[$i][$j];
797            
798             }
799            
800             }
801              
802             # get 'matf' offset
803 0           $value = $matf->offset;
804              
805             # verify number of elements
806 0 0         ($#{$value} < 3) or croak('\'matf\' offset has more than 3 elements');
  0            
807              
808             # for each element
809 0           for my $i (0 .. $#{$value}) {
  0            
810            
811             # verify array element is a number
812 0 0         (Scalar::Util::looks_like_number($value->[$i])) or croak('\'matf\' offset element not numeric');
813            
814             # copy offset to object
815 0           $self->[1][$i][3] = $value->[$i];
816            
817             }
818            
819             }
820              
821             # make new ratfunc object from attribute hash
822             # hash keys are: ('header', 'matrix', 'offset')
823             # object elements not specified in the hash are unchanged
824             # parameters: (ref_to_object, ref_to_attribute_hash)
825             sub _new_from_hash {
826              
827             # get parameters
828 0     0     my ($self, $hash) = @_;
829              
830             # local variables
831 0           my ($value);
832              
833             # make identity matrix (6x4)
834 0           $self->[1] = bless([
835             [1, 0, 0, 0],
836             [0, 1, 0, 0],
837             [0, 0, 1, 0],
838             [0, 0, 0, 1],
839             [0, 0, 0, 1],
840             [0, 0, 0, 1],
841             ], 'Math::Matrix');
842              
843             # if 'header' key defined
844 0 0         if (defined($hash->{'header'})) {
845            
846             # if reference to hash
847 0 0         if (ref($hash->{'header'}) eq 'HASH') {
848            
849             # set object element
850 0           $self->[0] = {%{$hash->{'header'}}};
  0            
851            
852             } else {
853            
854             # wrong data type
855 0           croak('wrong \'header\' data type');
856            
857             }
858            
859             }
860              
861             # if 'matrix' key defined
862 0 0         if (defined($hash->{'matrix'})) {
863            
864             # get value
865 0           $value = $hash->{'matrix'};
866            
867             # if a reference to a 2-D array -or- Math::Matrix object
868 0 0 0       if ((ref($value) eq 'ARRAY' && @{$value} == grep {ref() eq 'ARRAY'} @{$value}) || UNIVERSAL::isa($value, 'Math::Matrix')) {
  0   0        
  0            
  0            
869            
870             # verify number of rows
871 0 0         ($#{$value} < 6) or croak('\'matrix\' array has more than 6 rows');
  0            
872            
873             # for each row
874 0           for my $i (0 .. $#{$value}) {
  0            
875            
876             # verify number of columns
877 0 0         ($#{$value->[$i]} < 4) or croak('\'matrix\' array has more than 4 columns');
  0            
878            
879             # for each column
880 0           for my $j (0 .. $#{$value->[$i]}) {
  0            
881            
882             # verify matrix element is a number
883 0 0         (Scalar::Util::looks_like_number($value->[$i][$j])) or croak('\'matrix\' element not numeric');
884            
885             # copy matrix element
886 0           $self->[1][$i][$j] = $value->[$i][$j];
887            
888             }
889            
890             }
891            
892             } else {
893            
894             # wrong data type
895 0           croak('wrong \'matrix\' data type');
896            
897             }
898            
899             }
900              
901             # if 'offset' key defined
902 0 0         if (defined($hash->{'offset'})) {
903            
904             # get value
905 0           $value = $hash->{'offset'};
906            
907             # if a reference to an array of scalars
908 0 0 0       if (ref($value) eq 'ARRAY' && @{$value} == grep {! ref()} @{$value}) {
  0            
  0            
  0            
909            
910             # verify number of elements
911 0 0         ($#{$value} < 3) or croak('\'offset\' array has more than 3 elements');
  0            
912            
913             # for each element
914 0           for my $i (0 .. $#{$value}) {
  0            
915            
916             # verify array element is a number
917 0 0         (Scalar::Util::looks_like_number($value->[$i])) or croak('\'offset\' element not numeric');
918            
919             # copy offset to object
920 0           $self->[1][$i][3] = $value->[$i];
921            
922             }
923            
924             } else {
925            
926             # wrong data type
927 0           croak('wrong \'offset\' data type');
928            
929             }
930            
931             }
932            
933             }
934              
935             1;