File Coverage

blib/lib/ICC/Profile/matf.pm
Criterion Covered Total %
statement 50 449 11.1
branch 9 198 4.5
condition 1 108 0.9
subroutine 11 38 28.9
pod 1 20 5.0
total 72 813 8.8


line stmt bran cond sub pod time code
1             package ICC::Profile::matf;
2              
3 7     7   174422 use strict;
  7         47  
  7         161  
4 7     7   31 use Carp;
  7         10  
  7         374  
5              
6             our $VERSION = 0.33;
7              
8             # revised 2018-08-07
9             #
10             # Copyright © 2004-2020 by William B. Birkett
11              
12             # inherit from Shared
13 7     7   774 use parent qw(ICC::Shared);
  7         490  
  7         46  
14              
15             # enable static variables
16 7     7   306 use feature 'state';
  7         13  
  7         36758  
17              
18             # create new matf object
19             # hash keys are: ('header', 'matrix', 'offset')
20             # 'header' value is a hash reference
21             # 'matrix' value is a 2D array reference -or- Math::Matrix object -or- positive integer
22             # 'offset' value is a 1D array reference -or- numeric value
23             # when the 'matrix' value is a positive integer, an identity matrix of that size is created
24             # when the 'offset' value is a numeric value, an array containing that value is created
25             # when the parameters are input and output arrays, the 'fit' method is called on the object
26             # parameters: ([ref_to_attribute_hash])
27             # parameters: (ref_to_input_array, ref_to_output_array, [offset_flag])
28             # returns: (ref_to_object)
29             sub new {
30              
31             # get object class
32 5     5 0 829 my $class = shift();
33              
34             # create empty matf object
35 5         15 my $self = [
36             {}, # header
37             [], # matrix
38             [] # offset
39             ];
40            
41             # local parameter
42 5         8 my ($info);
43              
44             # if there are parameters
45 5 50       15 if (@_) {
46            
47             # if one parameter, a hash reference
48 0 0 0     0 if (@_ == 1 && ref($_[0]) eq 'HASH') {
    0 0        
49            
50             # make new matf object from attribute hash
51 0         0 _new_from_hash($self, shift());
52            
53             # if two or three parameters
54             } elsif (@_ == 2 || @_ == 3) {
55            
56             # fit the object to data
57 0 0       0 ($info = fit($self, @_)) && croak("'matf' fit operation failed with error $info");
58            
59             } else {
60            
61             # error
62 0         0 croak('\'matf\' invalid parameter(s)');
63            
64             }
65            
66             }
67              
68             # bless object
69 5         11 bless($self, $class);
70              
71             # return object reference
72 5         12 return($self);
73              
74             }
75              
76             # make CAT (chromatic adaptation transform) object
77             # using linear Bradford transform (see Annex E of 'ICC1v43_2010-12.pdf')
78             # default PCS is ICC D50, normalized to adopted white point (SRC)
79             # parameters: (src_XYZ_vector, [pcs_XYZ_vector])
80             # returns: (ref_to_object)
81             sub bradford {
82              
83             # get object class
84 0     0 0 0 my $class = shift();
85              
86             # create empty matf object
87 0         0 my $self = [
88             {}, # header
89             [], # matrix
90             [] # offset
91             ];
92              
93             # local variables
94 0         0 my ($brad, $srct, $pcst, $ratio);
95              
96             # get parameters
97 0         0 my ($src, $pcs) = @_;
98              
99             # if pcs values are undefined
100 0 0       0 if (! defined($pcs)) {
101            
102             # set pcs xyz values to ICC D50 (normalized)
103 0         0 $pcs = [map {$_ * $src->[1]} 0.9642, 1, 0.8249];
  0         0  
104            
105             }
106              
107             # make Bradford matrix
108 0         0 $brad = Math::Matrix->new(
109             [0.8951, 0.2664, -0.1614],
110             [-0.7502, 1.7135, 0.0367],
111             [0.0389, -0.0685, 1.0296]
112             );
113              
114             # compute pcs cone values
115 0         0 $pcst = $brad * (Math::Matrix->new($pcs)->transpose);
116              
117             # compute src cone values
118 0         0 $srct = $brad * (Math::Matrix->new($src)->transpose);
119              
120             # make cone ratio matrix
121 0         0 $ratio = Math::Matrix->new(
122             [$srct->[0][0]/$pcst->[0][0], 0, 0],
123             [0, $srct->[1][0]/$pcst->[1][0], 0],
124             [0, 0, $srct->[2][0]/$pcst->[2][0]]
125             );
126              
127             # set header
128 0         0 $self->[0] = {'src' => $src, 'pcs' => $pcs, 'type' => 'bradford'};
129              
130             # set matrix
131 0         0 $self->[1] = ($ratio * $brad)->concat($brad)->solve;
132              
133             # bless object
134 0         0 bless($self, $class);
135              
136             # return object reference
137 0         0 return($self);
138              
139             }
140              
141             # make CAT (chromatic adaptation transform) object
142             # using CAT02 transform (see CIE CIECAM02)
143             # default PCS is ICC D50, normalized to adopted white point (SRC)
144             # parameters: (src_XYZ_vector, [pcs_XYZ_vector])
145             # returns: (ref_to_object)
146             sub cat02 {
147              
148             # get object class
149 0     0 0 0 my $class = shift();
150              
151             # create empty matf object
152 0         0 my $self = [
153             {}, # header
154             [], # matrix
155             [] # offset
156             ];
157              
158             # local variables
159 0         0 my ($cat02, $srct, $pcst, $ratio);
160              
161             # get parameters
162 0         0 my ($src, $pcs) = @_;
163              
164             # if pcs values are undefined
165 0 0       0 if (! defined($pcs)) {
166            
167             # set pcs xyz values to ICC D50 (normalized)
168 0         0 $pcs = [map {$_ * $src->[1]} 0.9642, 1, 0.8249];
  0         0  
169            
170             }
171              
172             # make CAT02 matrix
173 0         0 $cat02 = Math::Matrix->new(
174             [0.7328, 0.4296, -0.1624],
175             [-0.7036, 1.6975, 0.0061],
176             [0.0030, 0.0136, 0.9834]
177             );
178              
179             # compute pcs cone values
180 0         0 $pcst = $cat02 * (Math::Matrix->new($pcs)->transpose);
181              
182             # compute src cone values
183 0         0 $srct = $cat02 * (Math::Matrix->new($src)->transpose);
184              
185             # make cone ratio matrix
186 0         0 $ratio = Math::Matrix->new(
187             [$srct->[0][0]/$pcst->[0][0], 0, 0],
188             [0, $srct->[1][0]/$pcst->[1][0], 0],
189             [0, 0, $srct->[2][0]/$pcst->[2][0]]
190             );
191              
192             # set header
193 0         0 $self->[0] = {'src' => $src, 'pcs' => $pcs, 'type' => 'cat02'};
194              
195             # set matrix
196 0         0 $self->[1] = ($ratio * $cat02)->concat($cat02)->solve;
197              
198             # bless object
199 0         0 bless($self, $class);
200              
201             # return object reference
202 0         0 return($self);
203              
204             }
205              
206             # make CAT (chromatic adaptation transform) object
207             # just scales the src XYZ values to the pcs, not a true CAT
208             # default PCS is ICC D50, normalized to adopted white point (SRC)
209             # parameters: (src_XYZ_vector, [pcs_XYZ_vector])
210             # returns: (ref_to_object)
211             sub quasi {
212              
213             # get object class
214 0     0 0 0 my $class = shift();
215              
216             # create empty matf object
217 0         0 my $self = [
218             {}, # header
219             [], # matrix
220             [] # offset
221             ];
222              
223             # get parameters
224 0         0 my ($src, $pcs) = @_;
225              
226             # if pcs values are undefined
227 0 0       0 if (! defined($pcs)) {
228            
229             # set pcs xyz values to ICC D50 (normalized)
230 0         0 $pcs = [map {$_ * $src->[1]} (0.9642, 1, 0.8249)];
  0         0  
231            
232             }
233              
234             # set header
235 0         0 $self->[0] = {'src' => $src, 'pcs' => $pcs, 'type' => 'quasi'};
236              
237             # set matrix
238 0         0 $self->[1] = Math::Matrix->diagonal($pcs->[0]/$src->[0], $pcs->[1]/$src->[1], $pcs->[2]/$src->[2]);
239              
240             # bless object
241 0         0 bless($self, $class);
242              
243             # return object reference
244 0         0 return($self);
245              
246             }
247              
248             # create inverse 'matf' object
249             # requires the matrix element to be square
250             # returns: (ref_to_object)
251             sub inv {
252              
253             # get object
254 0     0 0 0 my $self = shift();
255              
256             # local variables
257 0         0 my ($inv, $sys);
258              
259             # make new empty object
260 0         0 $inv = ICC::Profile::matf->new();
261              
262             # if matrix is not empty
263 0 0       0 if (defined($self->[1][0][0])) {
264            
265             # verify matrix is square
266 0 0       0 (@{$self->[1]} == @{$self->[1][0]}) or croak('matrix must be square');
  0         0  
  0         0  
267            
268             # invert matrix
269 0         0 $inv->[1] = $self->[1]->invert();
270            
271             # if offset is not empty
272 0 0       0 if (defined($self->[2][0])) {
273            
274             # clone the parent matrix
275 0         0 $sys = Storable::dclone($self->[1]);
276            
277             # for each matrix row
278 0         0 for my $i (0 .. $#{$sys}) {
  0         0  
279            
280             # concatenate negative offsets
281 0         0 push(@{$sys->[$i]}, -$self->[2][$i]);
  0         0  
282            
283             }
284            
285             # solve for new offsets
286 0         0 $inv->[2] = $sys->solve->transpose->[0];
287            
288             }
289            
290             }
291              
292             # return object
293 0         0 return($inv);
294              
295             }
296              
297             # get/set reference to header hash
298             # parameters: ([ref_to_new_hash])
299             # returns: (ref_to_hash)
300             sub header {
301            
302             # get object reference
303 0     0 0 0 my $self = shift();
304            
305             # if there are parameters
306 0 0       0 if (@_) {
307            
308             # if one parameter, a hash reference
309 0 0 0     0 if (@_ == 1 && ref($_[0]) eq 'HASH') {
310            
311             # set header to new hash
312 0         0 $self->[0] = {%{shift()}};
  0         0  
313            
314             } else {
315            
316             # error
317 0         0 croak('\'matf\' header attribute must be a hash reference');
318            
319             }
320            
321             }
322            
323             # return reference
324 0         0 return($self->[0]);
325            
326             }
327              
328             # get/set reference to matrix array
329             # parameters: ([ref_to_new_array])
330             # returns: (ref_to_array)
331             sub matrix {
332            
333             # get object reference
334 2     2 0 4 my $self = shift();
335            
336             # if there are parameters
337 2 50       7 if (@_) {
338            
339             # if one parameter, a 2-D array reference
340 0 0 0     0 if (@_ == 1 && ref($_[0]) eq 'ARRAY' && @{$_[0]} == grep {ref() eq 'ARRAY'} @{$_[0]}) {
  0 0 0     0  
  0   0     0  
  0         0  
341            
342             # set matrix to clone of array
343 0         0 $self->[1] = bless(Storable::dclone($_[0]), 'Math::Matrix');
344            
345             # if one parameter, a Math::Matrix object
346             } elsif (@_ == 1 && UNIVERSAL::isa($_[0], 'Math::Matrix')) {
347            
348             # set matrix to object
349 0         0 $self->[1] = $_[0];
350            
351             } else {
352            
353             # error
354 0         0 croak('\'matf\' matrix must be a 2-D array reference or Math::Matrix object');
355            
356             }
357            
358             }
359            
360             # return object reference
361 2         5 return($self->[1]);
362            
363             }
364              
365             # get/set reference to offset array
366             # parameters: ([ref_to_new_array])
367             # returns: (ref_to_array)
368             sub offset {
369            
370             # get object reference
371 4     4 0 7 my $self = shift();
372            
373             # if there are parameters
374 4 50       9 if (@_) {
375            
376             # if one parameter, an array reference
377 0 0 0     0 if (@_ == 1 && ref($_[0]) eq 'ARRAY' && @{$_[0]} == grep {! ref()} @{$_[0]}) {
  0   0     0  
  0         0  
  0         0  
378            
379             # set offset to copy of array
380 0         0 $self->[2] = [@{shift()}];
  0         0  
381            
382             } else {
383            
384             # error
385 0         0 croak('\'matf\' offset must be an array reference');
386            
387             }
388            
389             }
390            
391             # return reference
392 4         18 return($self->[2]);
393            
394             }
395              
396             # get/set equivalent matrix-based profile primaries
397             # see appendix F.3 of 'ICC1v43_2010-12.pdf'
398             # each matrix row contains an XYZ primary
399             # parameters: ([Math::Matrix_object -or- ref_to_array])
400             # returns: (Math::Matrix_object)
401             sub primary {
402              
403             # get parameters
404 0     0 0 0 my ($self, $pri) = @_;
405              
406             # if primaries parameter is supplied
407 0 0       0 if (defined($pri)) {
408            
409             # if 3x3 Math::Matrix object or array
410 0 0 0     0 if ((UNIVERSAL::isa($pri, 'Math::Matrix') || ref($pri) eq 'ARRAY') && @{$pri} == 3 && @{$pri->[0]} == 3) {
  0   0     0  
  0   0     0  
411            
412             # compute matrix object from primary tags
413 0         0 $self->[1] = Math::Matrix->new(@{$pri})->transpose->multiply_scalar(32768/65535);
  0         0  
414            
415             } else {
416            
417             # error
418 0         0 croak('invalid primary matrix parameter');
419            
420             }
421            
422             } else {
423            
424             # if 'matf' matrix is defined
425 0 0       0 if (defined($self->[1])) {
426            
427             # compute primary tags from matrix object
428 0         0 $pri = $self->[1]->multiply_scalar(65535/32768)->transpose();
429            
430             } else {
431            
432             # error
433 0         0 croak('\'matf\' object has no matrix');
434            
435             }
436            
437             }
438              
439             # return
440 0         0 return($pri);
441              
442             }
443              
444             # fit matf object to data
445             # uses LAPACK dgels function to perform a least-squares fit
446             # fitting is done with or without offset, according to offset_flag
447             # input and output are 2D array references -or- Math::Matrix objects
448             # parameters: (ref_to_input_array, ref_to_output_array, [offset_flag])
449             # returns: (dgels_info_value)
450             sub fit {
451              
452             # get parameters
453 0     0 0 0 my ($self, $in, $out, $oflag) = @_;
454              
455             # local variables
456 0         0 my ($info, $ab);
457              
458             # check if ICC::Support::Lapack module is loaded
459 0         0 state $lapack = defined($INC{'ICC/Support/Lapack.pm'});
460              
461             # verify ICC::Support::Lapack module is loaded
462 0 0       0 ($lapack) or croak('\'fit\' method requires ICC::Support::Lapack module');
463              
464             # resolve offset flag
465 0 0       0 $oflag = 0 if (! defined($oflag));
466              
467             # verify input array
468 0 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        
469              
470             # verify output array
471 0 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        
472              
473             # verify array dimensions
474 0 0       0 ($#{$in} == $#{$out}) or croak('\'fit\' input and output arrays have different number of rows');
  0         0  
  0         0  
475              
476             # fit the matrix
477 0         0 ($info, $ab) = ICC::Support::Lapack::matf_fit($in, $out, $oflag);
478              
479             # check result
480 0 0       0 carp('fit failed - bad parameter when calling dgels') if ($info < 0);
481 0 0       0 carp('fit failed - A matrix not full rank') if ($info > 0);
482              
483             # initialize matrix object
484 0         0 $self->[1] = Math::Matrix->new([]);
485              
486             # for each input
487 0         0 for my $i (0 .. $#{$in->[0]}) {
  0         0  
488            
489             # for each output
490 0         0 for my $j (0 .. $#{$out->[0]}) {
  0         0  
491            
492             # set matrix element (transposing)
493 0         0 $self->[1][$j][$i] = $ab->[$i][$j];
494            
495             }
496            
497             }
498            
499             # if offset flag
500 0 0       0 if ($oflag) {
501            
502             # set offset
503 0         0 $self->[2] = [@{$ab->[$#{$in->[0]} + 1]}];
  0         0  
  0         0  
504            
505             } else {
506            
507             # no offset
508 0         0 $self->[2] = [];
509            
510             }
511              
512             # return info value
513 0         0 return($info);
514              
515             }
516              
517             # transform data
518             # supported input types:
519             # parameters: (list, [hash])
520             # parameters: (vector, [hash])
521             # parameters: (matrix, [hash])
522             # parameters: (Math::Matrix_object, [hash])
523             # parameters: (structure, [hash])
524             # returns: (same_type_as_input)
525             sub transform {
526              
527             # set hash value (0 or 1)
528 0 0   0 0 0 my $h = ref($_[-1]) eq 'HASH' ? 1 : 0;
529              
530             # if input a 'Math::Matrix' object
531 0 0 0     0 if (@_ == $h + 2 && UNIVERSAL::isa($_[1], 'Math::Matrix')) {
    0 0        
    0          
532            
533             # call matrix transform
534 0         0 &_trans2;
535            
536             # if input an array reference
537             } elsif (@_ == $h + 2 && ref($_[1]) eq 'ARRAY') {
538            
539             # if array contains numbers (vector)
540 0 0 0     0 if (! ref($_[1][0]) && @{$_[1]} == grep {Scalar::Util::looks_like_number($_)} @{$_[1]}) {
  0 0 0     0  
  0         0  
  0         0  
541            
542             # call vector transform
543 0         0 &_trans1;
544            
545             # if array contains vectors (2-D array)
546 0 0       0 } elsif (ref($_[1][0]) eq 'ARRAY' && @{$_[1]} == grep {ref($_) eq 'ARRAY' && Scalar::Util::looks_like_number($_->[0])} @{$_[1]}) {
  0         0  
  0         0  
547            
548             # call matrix transform
549 0         0 &_trans2;
550            
551             } else {
552            
553             # call structure transform
554 0         0 &_trans3;
555            
556             }
557            
558             # if input a list (of numbers)
559 0         0 } elsif (@_ == $h + 1 + grep {Scalar::Util::looks_like_number($_)} @_) {
560            
561             # call list transform
562 0         0 &_trans0;
563            
564             } else {
565            
566             # error
567 0         0 croak('invalid transform input');
568            
569             }
570              
571             }
572              
573             # inverse transform
574             # note: number of undefined output values must equal number of defined input values
575             # note: input array contains the final calculated input values upon return
576             # parameters: (ref_to_input_array, ref_to_output_array)
577             sub inverse {
578              
579             # get parameters
580 0     0 0 0 my ($self, $in, $out) = @_;
581              
582             # local variables
583 0         0 my ($i, $j, @si, @so);
584 0         0 my ($int, $info, $delta, $sys, $res, $mat);
585              
586             # check if ICC::Support::Lapack module is loaded
587 0         0 state $lapack = defined($INC{'ICC/Support/Lapack.pm'});
588              
589             # initialize indices
590 0         0 $i = $j = -1;
591              
592             # build slice arrays while validating input and output arrays
593 0 0       0 ((grep {$i++; defined() && push(@si, $i)} @{$in}) == (grep {$j++; ! defined() && push(@so, $j)} @{$out})) or croak('wrong number of undefined values');
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
594              
595             # for each undefined output value
596 0         0 for my $i (@so) {
597            
598             # set to 0
599 0         0 $out->[$i] = 0;
600            
601             }
602              
603             # if ICC::Support::Lapack module is loaded
604 0 0       0 if ($lapack) {
605            
606             # compute initial transform values
607 0         0 $int = ICC::Support::Lapack::matf_vec_trans($out, $self->[1], $self->[2]);
608            
609             # for each input
610 0         0 for my $i (0 .. $#si) {
611            
612             # for each output
613 0         0 for my $j (0 .. $#so) {
614            
615             # copy Jacobian value to system matrix
616 0         0 $sys->[$i][$j] = $self->[1][$si[$i]][$so[$j]];
617            
618             }
619            
620             # compute residual value
621 0         0 $res->[$i][0] = $in->[$si[$i]] - $int->[$si[$i]];
622            
623             }
624            
625             # solve for delta values
626 0         0 ($info, $delta) = ICC::Support::Lapack::solve($sys, $res);
627            
628             # report linear system error
629 0 0       0 ($info) && print "matf inverse error $info: @{$in}\n";
  0         0  
630            
631             # for each output value
632 0         0 for my $i (0 .. $#so) {
633            
634             # add delta value
635 0         0 $out->[$so[$i]] += $delta->[$i][0];
636            
637             }
638            
639             # compute final transform values
640 0         0 @{$in} = @{ICC::Support::Lapack::matf_vec_trans($out, $self->[1], $self->[2])};
  0         0  
  0         0  
641            
642             } else {
643            
644             # compute initial transform values
645 0         0 $int = [_trans0($self, @{$out})];
  0         0  
646            
647             # for each input
648 0         0 for my $i (0 .. $#si) {
649            
650             # for each output
651 0         0 for my $j (0 .. $#so) {
652            
653             # copy Jacobian value to solution matrix
654 0         0 $mat->[$i][$j] = $self->[1][$si[$i]][$so[$j]];
655            
656             }
657            
658             # save residual value to solution matrix
659 0         0 $mat->[$i][$#si + 1] = $in->[$si[$i]] - $int->[$si[$i]];
660            
661             }
662            
663             # bless Matrix
664 0         0 bless($mat, 'Math::Matrix');
665            
666             # solve for delta values
667 0   0     0 $delta = $mat->solve || print "matf inverse error: @{$in}\n";
668            
669             # for each output value
670 0         0 for my $i (0 .. $#so) {
671            
672             # add delta value
673 0         0 $out->[$so[$i]] += $delta->[$i][0];
674            
675             }
676            
677             # compute final transform values
678 0         0 @{$in} = _trans0($self, @{$out});
  0         0  
  0         0  
679            
680             }
681            
682             }
683              
684             # compute Jacobian matrix
685             # note: input values only required for output values
686             # parameters: ([input_vector])
687             # returns: (ref_to_Jacobian_matrix, [output_vector])
688             sub jacobian {
689              
690             # get object reference
691 0     0 0 0 my $self = shift();
692              
693             # if output values wanted
694 0 0       0 if (wantarray) {
695            
696             # return Jacobian and output values
697 0         0 return(bless(Storable::dclone($self->[1]), 'Math::Matrix'), _trans1($self, $_[0]));
698            
699             } else {
700            
701             # return Jacobian only
702 0         0 return(bless(Storable::dclone($self->[1]), 'Math::Matrix'));
703            
704             }
705            
706             }
707              
708             # invert data
709             # requires the matrix element to be square
710             # supported input types:
711             # parameters: (list, [hash])
712             # parameters: (vector, [hash])
713             # parameters: (matrix, [hash])
714             # parameters: (Math::Matrix_object, [hash])
715             # parameters: (structure, [hash])
716             # returns: (same_type_as_input)
717             sub invsqr {
718              
719             # set hash value (0 or 1)
720 0 0   0 0 0 my $h = ref($_[-1]) eq 'HASH' ? 1 : 0;
721              
722             # if input a 'Math::Matrix' object
723 0 0 0     0 if (@_ == $h + 2 && UNIVERSAL::isa($_[1], 'Math::Matrix')) {
    0 0        
    0          
724            
725             # call matrix transform
726 0         0 &_invsqr2;
727            
728             # if input an array reference
729             } elsif (@_ == $h + 2 && ref($_[1]) eq 'ARRAY') {
730            
731             # if array contains numbers (vector)
732 0 0 0     0 if (! ref($_[1][0]) && @{$_[1]} == grep {Scalar::Util::looks_like_number($_)} @{$_[1]}) {
  0 0 0     0  
  0         0  
  0         0  
733            
734             # call vector transform
735 0         0 &_invsqr1;
736            
737             # if array contains vectors (2-D array)
738 0 0       0 } elsif (ref($_[1][0]) eq 'ARRAY' && @{$_[1]} == grep {ref($_) eq 'ARRAY' && Scalar::Util::looks_like_number($_->[0])} @{$_[1]}) {
  0         0  
  0         0  
739            
740             # call matrix transform
741 0         0 &_invsqr2;
742            
743             } else {
744            
745             # call structure transform
746 0         0 &_invsqr3;
747            
748             }
749            
750             # if input a list (of numbers)
751 0         0 } elsif (@_ == $h + 1 + grep {Scalar::Util::looks_like_number($_)} @_) {
752            
753             # call list transform
754 0         0 &_invsqr0;
755            
756             } else {
757            
758             # error
759 0         0 croak('invalid transform input');
760            
761             }
762              
763             }
764              
765             # create matf object from ICC profile
766             # parameters: (ref_to_parent_object, file_handle, ref_to_tag_table_entry)
767             # returns: (ref_to_object)
768             sub new_fh {
769              
770             # get object class
771 0     0 0 0 my $class = shift();
772              
773             # create empty matf object
774 0         0 my $self = [
775             {}, # header
776             [], # matrix
777             [] # offset
778             ];
779              
780             # verify 3 parameters
781 0 0       0 (@_ == 3) or croak('wrong number of parameters');
782              
783             # read matf data from profile
784 0         0 _readICCmatf($self, @_);
785              
786             # bless object
787 0         0 bless($self, $class);
788              
789             # return object reference
790 0         0 return($self);
791              
792             }
793              
794             # writes matf object to ICC profile
795             # parameters: (ref_to_parent_object, file_handle, ref_to_tag_table_entry)
796             sub write_fh {
797              
798             # verify 4 parameters
799 0 0   0 0 0 (@_ == 4) or croak('wrong number of parameters');
800              
801             # write matf data to profile
802 0         0 goto &_writeICCmatf;
803              
804             }
805              
806             # get tag size (for writing to profile)
807             # returns: (clut_size)
808             sub size {
809              
810             # get parameter
811 0     0 0 0 my $self = shift();
812              
813             # set header size
814 0         0 my $size = 12;
815              
816             # add matrix and offset size
817 0         0 $size += 4 * @{$self->[1]} * (@{$self->[1][0]} + 1);
  0         0  
  0         0  
818              
819             # return size
820 0         0 return($size);
821              
822             }
823              
824             # get number of input channels
825             # returns: (number)
826             sub cin {
827              
828             # get object reference
829 6     6 0 28 my $self = shift();
830              
831             # return
832 6         11 return(scalar(@{$self->[1][0]}));
  6         173  
833              
834             }
835              
836             # get number of output channels
837             # returns: (number)
838             sub cout {
839              
840             # get object reference
841 6     6 0 11 my $self = shift();
842              
843             # return
844 6         9 return(scalar(@{$self->[1]}));
  6         24  
845              
846             }
847              
848             # print object contents to string
849             # format is an array structure
850             # parameter: ([format])
851             # returns: (string)
852             sub sdump {
853              
854             # get parameters
855 0     0 1 0 my ($self, $p) = @_;
856              
857             # local variables
858 0         0 my ($fmt, $s, $rows, $off, $fn);
859              
860             # resolve parameter to an array reference
861 0 0       0 $p = defined($p) ? ref($p) eq 'ARRAY' ? $p : [$p] : [];
    0          
862              
863             # get format string
864 0 0 0     0 $fmt = defined($p->[0]) && ! ref($p->[0]) ? $p->[0] : 'm';
865              
866             # set string to object ID
867 0         0 $s = sprintf("'%s' object, (0x%x)\n", ref($self), $self);
868              
869             # get matrix rows
870 0         0 $rows = $#{$self->[1]};
  0         0  
871              
872             # get offset size
873 0         0 $off = $#{$self->[2]};
  0         0  
874              
875             # if empty object
876 0 0 0     0 if ($rows < 0 && $off < 0) {
877            
878             # append string
879 0         0 $s .= "\n";
880            
881             } else {
882            
883             # if matrix
884 0 0       0 if ($rows >= 0) {
885            
886             # append string
887 0         0 $s .= "matrix values\n";
888            
889             # for each row
890 0         0 for my $i (0 .. $rows) {
891            
892             # make number format
893 0         0 $fn = ' %10.5f' x @{$self->[1][$i]};
  0         0  
894            
895             # append matrix row
896 0         0 $s .= sprintf("$fn\n", @{$self->[1][$i]});
  0         0  
897            
898             }
899            
900             }
901            
902             # if offset
903 0 0       0 if ($off >= 0) {
904            
905             # append string
906 0         0 $s .= "offset values\n";
907            
908             # make number format
909 0         0 $fmt = ' %10.5f' x @{$self->[2]};
  0         0  
910            
911             # append offset values
912 0         0 $s .= sprintf("$fmt\n", @{$self->[2]});
  0         0  
913            
914             }
915            
916             }
917              
918             # return string
919 0         0 return($s);
920              
921             }
922              
923             # recursive transform
924             # array structure is traversed until scalar arrays are found and transformed
925             # parameters: (ref_to_object, subroutine_reference, input_array_reference, output_array_reference)
926             sub _crawl {
927              
928             # get parameters
929 0     0   0 my ($self, $sub, $in, $out) = @_;
930              
931             # if input is a vector (reference to a numeric array)
932 0 0       0 if (@{$in} == grep {Scalar::Util::looks_like_number($_)} @{$in}) {
  0         0  
  0         0  
  0         0  
933            
934             # transform input vector and copy to output
935 0         0 @{$out} = @{$sub->($self, $in)};
  0         0  
  0         0  
936            
937             } else {
938            
939             # for each input element
940 0         0 for my $i (0 .. $#{$in}) {
  0         0  
941            
942             # if an array reference
943 0 0       0 if (ref($in->[$i]) eq 'ARRAY') {
944            
945             # transform next level
946 0         0 _crawl($self, $sub, $in->[$i], $out->[$i] = []);
947            
948             } else {
949            
950             # error
951 0         0 croak('invalid input structure');
952            
953             }
954            
955             }
956            
957             }
958            
959             }
960              
961             # transform list
962             # parameters: (object_reference, list, [hash])
963             # returns: (list)
964             sub _trans0 {
965              
966             # local variables
967 0     0   0 my ($self, $hash, @out);
968              
969             # get object reference
970 0         0 $self = shift();
971              
972             # get optional hash
973 0 0       0 $hash = pop() if (ref($_[-1]) eq 'HASH');
974              
975             # validate number of input channels
976 0 0       0 (@_ == @{$self->[1][0]}) or croak('wrong number input channels');
  0         0  
977              
978             # set output to offset values or zeros
979 0 0       0 @out = defined($self->[2][0]) ? @{$self->[2]} : (0) x @{$self->[1]};
  0         0  
  0         0  
980              
981             # for each output
982 0         0 for my $i (0 .. $#{$self->[1]}) {
  0         0  
983            
984             # add matrix value
985 0         0 $out[$i] += ICC::Shared::dotProduct(\@_, $self->[1][$i]);
986            
987             }
988              
989             # return output data
990 0         0 return(@out);
991              
992             }
993              
994             # transform vector
995             # parameters: (object_reference, vector, [hash])
996             # returns: (vector)
997             sub _trans1 {
998              
999             # get parameters
1000 0     0   0 my ($self, $in, $hash) = @_;
1001              
1002             # check if ICC::Support::Lapack module is loaded
1003 0         0 state $lapack = defined($INC{'ICC/Support/Lapack.pm'});
1004              
1005             # validate number of input channels
1006 0 0       0 (@{$in} == @{$self->[1][0]}) or croak('wrong number input channels');
  0         0  
  0         0  
1007              
1008             # if ICC::Support::Lapack module is loaded
1009 0 0       0 if ($lapack) {
1010            
1011             # call the BLAS dgemv function
1012 0         0 return(ICC::Support::Lapack::matf_vec_trans($in, $self->[1], $self->[2]));
1013            
1014             } else {
1015            
1016             # return
1017 0         0 return([_trans0($self, @{$in})]);
  0         0  
1018            
1019             }
1020              
1021             }
1022              
1023             # transform matrix (2-D array -or- Math::Matrix object)
1024             # parameters: (object_reference, matrix, [hash])
1025             # returns: (matrix)
1026             sub _trans2 {
1027              
1028             # get parameters
1029 0     0   0 my ($self, $in, $hash) = @_;
1030              
1031             # local variables
1032 0         0 my ($info, $out, $offset);
1033              
1034             # check if ICC::Support::Lapack module is loaded
1035 0         0 state $lapack = defined($INC{'ICC/Support/Lapack.pm'});
1036              
1037             # validate number of input channels
1038 0 0       0 (@{$in->[0]} == @{$self->[1][0]}) or croak('wrong number input channels');
  0         0  
  0         0  
1039              
1040             # if ICC::Support::Lapack module is loaded
1041 0 0       0 if ($lapack) {
1042            
1043             # compute output matrix using BLAS dgemm function
1044 0         0 $out = ICC::Support::Lapack::matf_mat_trans($in, $self->[1], $self->[2]);
1045            
1046             } else {
1047            
1048             # get offset vector (zeros if undefined)
1049 0 0       0 $offset = defined($self->[2][0]) ? $self->[2] : [(0) x @{$in->[0]}];
  0         0  
1050            
1051             # make output array (from offset vector)
1052 0         0 $out = [map{Storable::dclone($offset)} (0 .. $#{$in})];
  0         0  
  0         0  
1053            
1054             # for each row
1055 0         0 for my $i (0 .. $#{$in}) {
  0         0  
1056            
1057             # for each column
1058 0         0 for my $j (0 .. $#{$self->[1]}) {
  0         0  
1059            
1060             # add dot product
1061 0         0 $out->[$i][$j] += ICC::Shared::dotProduct($in->[$i], $self->[1][$j]);
1062            
1063             }
1064            
1065             }
1066            
1067             }
1068              
1069             # return output matrix (Math::Matrix object or 2-D array)
1070 0 0       0 return(UNIVERSAL::isa($in, 'Math::Matrix') ? bless($out, 'Math::Matrix') : $out);
1071              
1072             }
1073              
1074             # transform structure
1075             # parameters: (object_reference, structure, [hash])
1076             # returns: (structure)
1077             sub _trans3 {
1078              
1079             # get parameters
1080 0     0   0 my ($self, $in, $hash) = @_;
1081              
1082             # transform the array structure
1083 0         0 _crawl($self, \&_trans1, $in, my $out = []);
1084              
1085             # return output structure
1086 0         0 return($out);
1087              
1088             }
1089              
1090             # invert list
1091             # parameters: (object_reference, list, [hash])
1092             # returns: (list)
1093             sub _invsqr0 {
1094              
1095             # local variables
1096 0     0   0 my ($self, $hash, @out);
1097              
1098             # get object reference
1099 0         0 $self = shift();
1100              
1101             # get optional hash
1102 0 0       0 $hash = pop() if (ref($_[-1]) eq 'HASH');
1103              
1104             # validate number of input channels
1105 0 0       0 (@_ == @{$self->[1][0]}) or croak('wrong number input channels');
  0         0  
1106              
1107             # return simple array
1108 0         0 return(@{_invsqr1($self, [@_])});
  0         0  
1109              
1110             }
1111              
1112             # invert vector
1113             # parameters: (object_reference, vector, [hash])
1114             # returns: (vector)
1115             sub _invsqr1 {
1116              
1117             # get parameters
1118 0     0   0 my ($self, $in, $hash) = @_;
1119              
1120             # validate number of input channels
1121 0 0       0 (@{$in} == @{$self->[1]}) or croak('wrong number input channels');
  0         0  
  0         0  
1122              
1123             # clone the 'matf' matrix
1124 0         0 my $sys = Storable::dclone($self->[1]);
1125              
1126             # for each input channel
1127 0         0 for my $i (0 .. $#{$in}) {
  0         0  
1128            
1129             # concatenate input value (minus offset, if any)
1130 0 0       0 push(@{$sys->[$i]}, defined($self->[2][$i]) ? $in->[$i] - $self->[2][$i] : $in->[$i]);
  0         0  
1131            
1132             }
1133              
1134             # return output vector
1135 0         0 return([map {$_->[0]} @{$sys->solve()}]);
  0         0  
  0         0  
1136              
1137             }
1138              
1139             # invert matrix (Math::Matrix object -or- 2-D array)
1140             # parameters: (object_reference, matrix, [hash])
1141             # returns: (output_matrix)
1142             sub _invsqr2 {
1143              
1144             # get parameters
1145 0     0   0 my ($self, $in, $hash) = @_;
1146              
1147             # local variables
1148 0         0 my ($info, $out, $sys);
1149              
1150             # check if ICC::Support::Lapack module is loaded
1151 0         0 state $lapack = defined($INC{'ICC/Support/Lapack.pm'});
1152              
1153             # validate number of input channels
1154 0 0       0 (@{$in->[0]} == @{$self->[1]}) or croak('wrong number input channels');
  0         0  
  0         0  
1155              
1156             # if ICC::Support::Lapack module is loaded
1157 0 0       0 if ($lapack) {
1158            
1159             # compute output matrix using Lapack DGESV function
1160 0         0 ($info, $out) = ICC::Support::Lapack::matf_inv($in, $self->[1], $self->[2]);
1161            
1162             # check for DGESV error
1163 0 0       0 ($info) && croak("'ICC::Support::Lapack::matf_inv' error: $info");
1164            
1165             # return output matrix (Math::Matrix object or 2-D array)
1166 0 0       0 return(UNIVERSAL::isa($in, 'Math::Matrix') ? bless($out, 'Math::Matrix') : $out);
1167            
1168             } else {
1169            
1170             # clone the 'matf' matrix
1171 0         0 $sys = Storable::dclone($self->[1]);
1172            
1173             # for each input channel
1174 0         0 for my $i (0 .. $#{$in->[0]}) {
  0         0  
1175            
1176             # for each data sample
1177 0         0 for my $j (0 .. $#{$in}) {
  0         0  
1178            
1179             # concatenate input value (minus offset, if any)
1180 0 0       0 push(@{$sys->[$i]}, defined($self->[2][$i]) ? $in->[$j][$i] - $self->[2][$i] : $in->[$j][$i]);
  0         0  
1181            
1182             }
1183            
1184             }
1185            
1186             # compute output matrix using 'Math::Matrix' methods
1187 0         0 $out = $sys->solve->transpose();
1188            
1189             # return output matrix (Math::Matrix object or 2-D array)
1190 0 0       0 return(UNIVERSAL::isa($in, 'Math::Matrix') ? $out : [@{$out}]);
  0         0  
1191            
1192             }
1193            
1194             }
1195              
1196             # invert structure
1197             # parameters: (object_reference, structure, [hash])
1198             # returns: (structure)
1199             sub _invsqr3 {
1200              
1201             # get parameters
1202 0     0   0 my ($self, $in, $hash) = @_;
1203              
1204             # transform the array structure
1205 0         0 _crawl($self, \&_invsqr1, $in, my $out = []);
1206              
1207             # return output structure
1208 0         0 return($out);
1209              
1210             }
1211              
1212             # make new matf object from attribute hash
1213             # hash keys are: ('header', 'matrix', 'offset')
1214             # object elements not specified in the hash are unchanged
1215             # parameters: (ref_to_object, ref_to_attribute_hash)
1216             sub _new_from_hash {
1217              
1218             # get parameters
1219 0     0   0 my ($self, $hash) = @_;
1220              
1221             # local variables
1222 0         0 my ($value);
1223            
1224             # if 'header' key defined
1225 0 0       0 if (defined($hash->{'header'})) {
1226            
1227             # if reference to hash
1228 0 0       0 if (ref($hash->{'header'}) eq 'HASH') {
1229            
1230             # set object element
1231 0         0 $self->[0] = {%{$hash->{'header'}}};
  0         0  
1232            
1233             } else {
1234            
1235             # wrong data type
1236 0         0 croak('wrong \'header\' data type');
1237            
1238             }
1239            
1240             }
1241            
1242             # if 'matrix' key defined
1243 0 0       0 if (defined($hash->{'matrix'})) {
1244            
1245             # get value
1246 0         0 $value = $hash->{'matrix'};
1247            
1248             # if a reference to a 2-D array
1249 0 0 0     0 if (ref($value) eq 'ARRAY' && @{$value} == grep {ref() eq 'ARRAY'} @{$value}) {
  0 0 0     0  
  0 0 0     0  
  0         0  
1250            
1251             # copy matrix to object
1252 0         0 $self->[1] = bless(Storable::dclone($value), 'Math::Matrix');
1253            
1254             # if a Math::Matrix object
1255             } elsif (UNIVERSAL::isa($value, 'Math::Matrix')) {
1256            
1257             # copy matrix to object
1258 0         0 $self->[1] = Storable::dclone($value);
1259            
1260             # if a positive integer
1261             } elsif (Scalar::Util::looks_like_number($value) && $value == int($value) && $value > 0) {
1262            
1263             # set matrix to identity matrix
1264 0         0 $self->[1] = Math::Matrix->new_identity($value);
1265            
1266             } else {
1267            
1268             # wrong data type
1269 0         0 croak('wrong \'matrix\' data type');
1270            
1271             }
1272            
1273             }
1274            
1275             # if 'offset' key defined
1276 0 0       0 if (defined($hash->{'offset'})) {
1277            
1278             # get value
1279 0         0 $value = $hash->{'offset'};
1280            
1281             # if a reference to an array of scalars
1282 0 0 0     0 if (ref($value) eq 'ARRAY' && @{$value} == grep {! ref()} @{$value}) {
  0 0       0  
  0         0  
  0         0  
1283            
1284             # copy offset to object
1285 0         0 $self->[2] = [@{$value}];
  0         0  
1286            
1287             # if a numeric value
1288             } elsif (Scalar::Util::looks_like_number($value)) {
1289            
1290             # if first 'matrix' row is defined
1291 0 0       0 if (defined($self->[1])) {
1292            
1293             # set offset to constant
1294 0         0 $self->[2] = [($value) x @{$self->[1]}];
  0         0  
1295            
1296             } else {
1297            
1298             # wrong data type
1299 0         0 croak('unknown \'matrix\' size');
1300            
1301             }
1302            
1303             } else {
1304            
1305             # wrong data type
1306 0         0 croak('wrong \'offset\' data type');
1307            
1308             }
1309            
1310             }
1311            
1312             }
1313              
1314             # read matf data
1315             # note: assumes file handle is positioned at start of matrix data
1316             # header information must be read separately by the calling function
1317             # setting offset flag enables reading of offset data following matrix data
1318             # number format is 2 (s15Fixed16Number) or 4 (floating point)
1319             # parameters: (ref_to_object, file_handle, input_channels, output_channels, offset_flag, format)
1320             sub _read_matf {
1321              
1322             # get parameters
1323 4     4   13 my ($self, $fh, $ci, $co, $oflag, $format) = @_;
1324              
1325             # local variables
1326 4         6 my ($buf);
1327              
1328             # if s15Fixed16Number format
1329 4 50       11 if ($format == 2) {
    0          
1330            
1331             # for each output channel
1332 4         15 for my $i (0 .. $co - 1) {
1333            
1334             # read into buffer
1335 12         39 read($fh, $buf, 4 * $ci);
1336            
1337             # unpack buffer and save
1338 12         35 $self->[1][$i] = [ICC::Shared::s15f162v(unpack('N*', $buf))];
1339            
1340             }
1341            
1342             # if offset data
1343 4 100       13 if ($oflag) {
1344            
1345             # read into buffer
1346 2         5 read($fh, $buf, 4 * $co);
1347            
1348             # unpack buffer and save
1349 2         6 $self->[2] = [ICC::Shared::s15f162v(unpack('N*', $buf))];
1350            
1351             }
1352            
1353             # if floating point format
1354             } elsif ($format == 4) {
1355            
1356             # for each output channel
1357 0         0 for my $i (0 .. $co - 1) {
1358            
1359             # read into buffer
1360 0         0 read($fh, $buf, 4 * $ci);
1361            
1362             # unpack buffer and save
1363 0         0 $self->[1][$i] = [unpack('f>*', $buf)];
1364            
1365             }
1366            
1367             # if offset data
1368 0 0       0 if ($oflag) {
1369            
1370             # read into buffer
1371 0         0 read($fh, $buf, 4 * $co);
1372            
1373             # unpack buffer and save
1374 0         0 $self->[2] = [unpack('f>*', $buf)];
1375            
1376             }
1377            
1378             } else {
1379            
1380             # error
1381 0         0 croak('unsupported format, must be 2 or 4');
1382            
1383             }
1384            
1385             # bless matrix array
1386 4         20 bless($self->[1], 'Math::Matrix');
1387            
1388             }
1389              
1390             # read matf tag from ICC profile
1391             # parameters: (ref_to_object, ref_to_parent_object, file_handle, ref_to_tag_table_entry)
1392             sub _readICCmatf {
1393              
1394             # get parameters
1395 0     0   0 my ($self, $parent, $fh, $tag) = @_;
1396              
1397             # local variables
1398 0         0 my ($buf, $ci, $co);
1399              
1400             # save tag signature
1401 0         0 $self->[0]{'signature'} = $tag->[0];
1402              
1403             # seek start of tag
1404 0         0 seek($fh, $tag->[1], 0);
1405              
1406             # read tag header
1407 0         0 read($fh, $buf, 12);
1408              
1409             # unpack header
1410 0         0 ($ci, $co) = unpack('x8 n2', $buf);
1411              
1412             # set number of input channels
1413 0         0 $self->[0]{'input_channels'} = $ci;
1414              
1415             # set number of output channels
1416 0         0 $self->[0]{'output_channels'} = $co;
1417              
1418             # read matrix w/offset
1419 0         0 _read_matf($self, $fh, $ci, $co, 1, 4);
1420              
1421             }
1422              
1423             # write matf data
1424             # note: assumes file handle is positioned at start of matf data
1425             # header information must be written separately by the calling function
1426             # setting offset flag enables writing of offset data following matrix data
1427             # if offset data array is undefined or empty, zeros are written
1428             # number format is 2 (s15Fixed16Number) or 4 (floating point)
1429             # parameters: (ref_to_object, file_handle, offset_flag, format)
1430             sub _write_matf {
1431              
1432             # get parameters
1433 2     2   6 my ($self, $fh, $oflag, $format) = @_;
1434              
1435             # local variables
1436 2         9 my ($buf);
1437              
1438             # if s15Fixed16Number format
1439 2 50       7 if ($format == 2) {
    0          
1440            
1441             # for each matrix row
1442 2         3 for my $i (0 .. $#{$self->[1]}) {
  2         7  
1443            
1444             # write matrix values as s15Fixed16Numbers
1445 6         16 print $fh pack('N*', ICC::Shared::v2s15f16(@{$self->[1][$i]}));
  6         20  
1446            
1447             }
1448            
1449             # if offset data
1450 2 50       10 if ($oflag) {
1451            
1452             # write offset values as s15Fixed16Numbers (if offset array is undefined or empty, write zeros)
1453 2 50 33     9 print $fh pack('N*', (defined($self->[2]) && @{$self->[2]} > 0) ? ICC::Shared::v2s15f16(@{$self->[2]}) : (0) x @{$self->[1][0]});
  2         6  
  0            
1454            
1455             }
1456            
1457             # if floating point format
1458             } elsif ($format == 4) {
1459            
1460             # for each matrix row
1461 0           for my $i (0 .. $#{$self->[1]}) {
  0            
1462            
1463             # write matrix values as big-endian 32-bit floating point
1464 0           print $fh pack('f>*', @{$self->[1][$i]});
  0            
1465            
1466             }
1467            
1468             # if offset data
1469 0 0         if ($oflag) {
1470            
1471             # write offset values as big-endian 32-bit floating point (if offset array is undefined or empty, write zeros)
1472 0 0 0       print $fh pack('f>*', (defined($self->[2]) && @{$self->[2]} > 0) ? @{$self->[2]} : (0) x @{$self->[1][0]});
  0            
  0            
1473            
1474             }
1475            
1476             } else {
1477            
1478             # error
1479 0           croak('unsupported format, must be 2 or 4');
1480            
1481             }
1482            
1483             }
1484              
1485             # write matf tag to ICC profile
1486             # parameters: (ref_to_object, ref_to_parent_object, file_handle, ref_to_tag_table_entry)
1487             sub _writeICCmatf {
1488              
1489             # get parameters
1490 0     0     my ($self, $parent, $fh, $tag) = @_;
1491              
1492             # local variables
1493 0           my ($ci, $co);
1494              
1495             # get number of input channels
1496 0           $ci = @{$self->[1][0]};
  0            
1497              
1498             # get number of output channels
1499 0           $co = @{$self->[1]};
  0            
1500            
1501             # validate number input channels (1 to 15)
1502 0 0 0       ($ci > 0 && $ci < 16) or croak('unsupported number of input channels');
1503              
1504             # validate number output channels (1 to 15)
1505 0 0 0       ($co > 0 && $co < 16) or croak('unsupported number of output channels');
1506              
1507             # seek start of tag
1508 0           seek($fh, $tag->[1], 0);
1509              
1510             # write 'matf' header
1511 0           print $fh pack('a4 x4 n2', 'matf', $ci, $co);
1512              
1513             # write matrix w/offset
1514 0           _write_matf($self, $fh, 1, 4);
1515              
1516             }
1517              
1518             1;