File Coverage

blib/lib/Color/TupleEncode/Baran.pm
Criterion Covered Total %
statement 87 97 89.6
branch 32 48 66.6
condition 17 24 70.8
subroutine 17 17 100.0
pod n/a
total 153 186 82.2


line stmt bran cond sub pod time code
1             package Color::TupleEncode::Baran;
2              
3 8     8   69 use warnings FATAL=>"all";
  8         14  
  8         427  
4 8     8   41 use strict;
  8         17  
  8         222  
5              
6 8     8   43 use Carp;
  8         15  
  8         557  
7 8     8   47 use Graphics::ColorObject;
  8         14  
  8         298  
8 8     8   49 use Color::TupleEncode;
  8         12  
  8         379  
9 8     8   6727 use Math::VecStat qw(min max);
  8         9495  
  8         588  
10 8     8   46 use POSIX qw(fmod);
  8         14  
  8         91  
11 8     8   7278 use Readonly;
  8         25434  
  8         13181  
12              
13             #use Smart::Comments;
14              
15             =head1 NAME
16              
17             Color::TupleEncode::Baran - a utility class for C that
18             implements color encoding of a 3-tuple C<(x,y,z)> to a color
19              
20             =head1 VERSION
21              
22             Version 0.11
23              
24             =cut
25              
26             our $VERSION = '0.11';
27              
28             =head1 SYNOPSIS
29              
30             This is a utility module used by L. This module
31             provides the default color encoding scheme. Therefore, if you do not
32             explicitly set the encoding method in a L object explicitly, it will be set to C
33              
34             To change or set the encoding method, pass the C
35             directly or as an option in C or set with C.
36             C
37              
38             %options = (-method=>"Color::TupleEncode::Baran");
39            
40             $encoder = Color::TupleEncode(options=>\%options);
41              
42             # using the direct setter
43              
44             $encoder->set_method("Color::TupleEncode::Baran");
45            
46             # setting method as an option individually
47              
48             $convert->set_options(-method=>"Color::TupleEncode::Baran");
49              
50             This module is not designed to be used directly.
51              
52             =head1 ENCODING ALGORITHM
53              
54             This module encodes a 3-tuple C<(x,y,z)> to a HSV color using the scheme described in
55              
56             Visualization of three-way comparisons of omics data
57             Richard Baran Martin Robert, Makoto Suematsu, Tomoyoshi Soga1 and Masaru Tomita
58             BMC Bioinformatics 2007, 8:72 doi:10.1186/1471-2105-8-72
59              
60             This publication can be accessed at L
61              
62             This class encodes a 3-tuple C<(x,y,z)> (or C<(a,b,c)> in accordance with the terminology in the publication) to a HSV color C<(h,s,v)>. The following parameters are supported
63              
64             # for hue default
65             -ha 0
66             -hb 20
67             -hc 240
68              
69             # for saturation - set using hash reference as
70             # option value, e.g. -saturation=>{dmin=>0.2,dmax=>0.8}
71             -saturation dmin 0
72             dmax 1
73             min 0
74             max 0
75             relative 0
76              
77             # for value - set using has reference as
78             # option value, e.g. -saturation=>{min=>0.2}
79             -value dmin NOT SET
80             dmax NOT SET
81             min 0
82             max 1
83             relative 0
84              
85             Options are set using
86              
87             %options=>{-ha=>60, -hb=>180, -hc=>300, -saturation=>{dmin=>0,dmax=>2}}
88              
89             $encoder = Color::TupleEncode(method=>"Color::TupleEncode::2Way",
90             options=>\%options);
91              
92             or
93              
94             $encoder->set_options(-ha=>60);
95             $encoder->set_options(-ha=>60, -saturation=>{dmin=>0,dmax=>2});
96              
97             See C for a chart of encoded colors.
98              
99             The color components are calculated as follows.
100              
101             =head2 Hue
102              
103             Given the tuple C<(a,b,c)>, let the characteristic hues for each tuple be C. Form the differences
104              
105             dab = | a - b |
106             dac = | a - c |
107             dbc = | b - c |
108              
109             The hue is calculated along the gradient formed by the two components that form the largest difference. For example, if C is the largest difference, the final hue lies along the gradient formed by C<(ha,hc)>.
110              
111             hue = 0 if a = b = c
112              
113             # values of hue below are fractional in the range [0,1] and
114             # always modulo 1 (e.g. hue=1.2 becomes 0.2).
115              
116             hue = ha + ( hb - ha ) * dbc / dab if dab >= dbc and dab >= dac
117              
118             hue = hb + ( hc - hb ) * dac / dbc if dbc > dab and dbc >= dac
119              
120             hue = hc + ( ha + 1 - hc ) * dab / dac if dac > dab and dac > dbc
121              
122             # convert from [0,1] to [0,360]
123              
124             hue = hue * 360
125              
126             The effect of this encoding is to emphasize the component that is the most different.
127              
128             If two components equal and the third is very different, e.g. C<(0.1,1,0.1)> then the encoded hue will the characteristic hue of the largest component. In this case C.
129              
130             When the difference in the close values is small C<(0.1,1,0.15)> the encoded hue will be very close to the characterstic hue of the most different component. In this case, the hue will be very close to C - the hue is C.
131              
132             When the values are spread equally C<(0.3,0.6,0.9)> the hue will be half way between the characteristic hues of the components that form the largest difference. In this case, the hue will lie between C and C - the hue is C.
133              
134             =head2 Saturation
135              
136             Given the tuple C<(a,b,c)> and the differences
137              
138             dab = | a - b |
139             dac = | a - c |
140             dbc = | b - c |
141              
142             let
143              
144             d = max( dab, dac, dbc )
145              
146             Saturation is given by
147              
148             s = 0 if d <= dmin
149            
150             s = 1 if d >= dmax
151            
152             s = ( d - dmin ) / ( dmax - dmin ) if dmin < d < dmax
153              
154             Thus, saturation is interpolated when the maximum difference C is within C<[ dmin, dmax ]>. These limits are set by C. For example
155              
156             $encoder->set_options( -saturation => { dmin => 0.25, dmax => 0.75 } );
157              
158             would result in saturation varying from its minimum to maximum value from C to C. Depending on the magnitude of the difference in components in your tuples, you will want to adjust the difference range to match.
159              
160             If the C<-relative> option is used, then a relative correction is applied to C if C 0> before saturation is calculated. Note that with this correction, C will always be in the range C<[ 0, 1 ]>.
161              
162             drel = d / max( |a|, |b|, |c|, d )
163              
164             d <- drel
165              
166             Saturation can be constrained within a range C<[ min, max ]> by setting the C parameters. These values must be in the range [0,1].
167              
168             $encoder->set_options( -saturation => { min => 0.25, max => 0.75 } );
169              
170             You can set C E C (e.g. saturation increases as C increases), or C E C (e.g. saturatio decreases as C increases).
171              
172             If either of C<(dmin,dmax)> parameters are not set, C always. You can clear a parameter by setting it to C.
173              
174             $encoder->set_options( -saturation => { -dmin => undef, -dmax => undef } )
175              
176             To toggle the use of relative difference,
177              
178             $encoder->set_options( -saturation => { relative => 1 } );
179              
180             The I publication in which this encoding was introduced suggests to use the product of absolute and relative saturations as the final saturation. This can be done by calculating two values of saturation, one with the C<-saturation=>{relative=>0}> option, and one with C<-saturation=>{relative=>1}>.
181              
182             You can combine saturation and value encoding together. See the L section.
183              
184             =head2 Value
185              
186             The value is defined analogously to saturation.
187              
188             You can supplement saturation encoding with value encoding as follows. Set the difference range C<[ dmin, dmax ]> for value to be higher/lower than the difference range for saturation. For example,
189              
190             $encoder->set_options(-saturation => { dmin => 0 , dmax => 2},
191             -value => { dmin => 2 , dmax => 5 , min => 1 , max => 0 };
192              
193             The effect will be to adjust saturation when the largest component difference is in the range C<[0,2]> (from C to C). Thus as the difference grows, the color becomes more saturated.
194              
195             In the range C<[ 2, 5 ]>, C since the range is beyond C set for saturation. However, in this higher range the value will be adjusted from C to C. Thus, as the difference grows, the color gets darker.
196              
197             Below is an example of the HSV values for various C<( x, y, z)> using the options above.
198              
199             0 , 0.1 , 1.0 251 0.50 1.0
200             0 , 0.1 , 1.5 248 0.75 1.0
201             0 , 0.1 , 2.0 246 1.00 1.0
202             0 , 0.1 , 3.0 243 1.00 0.67
203             0 , 0.1 , 4.0 242 1.00 0.33
204             0 , 0.1 , 5.0 242 1.00 0.00
205             0 , 0.1 , 6.0 242 1.00 0.00
206              
207             You can obtain these values with C as follows, for each tuple,
208              
209             > examples/example-3way -options "{-saturation=>{dmin=>0,dmax=>2},
210             -value=>{dmin=2,dmax=>5,min=>1,max=>0}}"
211             -tuple 0,0.1,1.5
212              
213              
214             =head1 EXPORT
215              
216             Exports nothing.
217              
218             Use L. The method implemented by this module is used by default.
219              
220             =cut
221              
222             =for comment
223             Given a data triplet, return the corresponding value.
224              
225             =cut
226              
227             Readonly::Scalar our $TUPLE_SIZE => 3;
228             Readonly::Array our @OPTIONS_OK => (qw(-ha -hb -hc -saturation -value));
229             Readonly::Hash our %OPTIONS_DEFAULT => (-ha=>0,-hb=>120,-hc=>240,-saturation=>{dmin=>0,dmax=>1});
230              
231             sub _get_value {
232 144     144   286 my $self = shift;
233 144         455 my ($a,$b,$c) = $self->get_tuple;
234 144         264 my ($dmin,$dmax);
235             # These are the hard limits on value.
236 144         226 my ($vmin,$vmax) = (1,0);
237             # Value options can be one or more of
238             # min, max, dmin, dmax, relative
239 144         393 my $options = $self->get_options(qw(-value));
240 144         405 return _get_interpolated_component($a,$b,$c,$vmin,$vmax,$options,"value");
241             }
242              
243              
244             =for comment
245             Given a data triplet, return the corresponding saturation
246              
247             =cut
248              
249             sub _get_saturation {
250 144     144   259 my $self = shift;
251 144         498 my ($a,$b,$c) = $self->get_tuple;
252 144         240 my ($s,$dmin,$dmax);
253 144         357 my ($smin,$smax) = (0,1);
254 144         384 my $options = $self->get_options(qw(-saturation));
255 144         432 return _get_interpolated_component($a,$b,$c,$smin,$smax,$options,"saturation");
256             }
257              
258             =for comment
259             Given a data triplet, return the corresponding hue.
260              
261             =cut
262              
263             sub _get_hue {
264 144     144   255 my $self = shift;
265 144         445 my ($a,$b,$c) = $self->get_tuple;
266 144         460 my ($ha,$hb,$hc) = $self->get_options(qw(-ha -hb -hc));
267 144 50       418 $ha /= 360 if $ha > 1;
268 144 50       384 $hb /= 360 if $hb > 1;
269 144 50       418 $hc /= 360 if $hc > 1;
270 144         173 my $h = 0;
271 144 100 100     1496 if($a == $b && $a == $c) {
    100 100        
    100 100        
    50 33        
272 16         20 $h = 0;
273             }
274             elsif (abs($a-$b) >= abs($b-$c) && abs($a-$b) >= abs($a-$c)) {
275 76         240 $h = $ha + ($hb-$ha)*abs($b-$c)/abs($a-$b);
276             }
277             elsif (abs($b-$c) > abs($a-$b) && abs($b-$c) >= abs($a-$c)) {
278 24         68 $h = $hb + ($hc-$hb)*abs($a-$c)/abs($b-$c);
279             }
280             elsif (abs($a-$c) > abs($a-$b) && abs($a-$c) > abs($b-$c)) {
281 28         92 $h = $hc + ($ha-$hc+1)*abs($a-$b)/abs($a-$c);
282 28         102 $h = fmod($h,1);
283             } else {
284 0         0 confess "couldn't find hue for $a,$b,$c";
285             }
286 144         1308 return 360*$h;
287             }
288              
289             =for comment
290             Common function for saturation and value. Interpolates the
291             tuple a,b,c between component_min and component_max. Options in $options control the process.
292              
293             =cut
294              
295             sub _get_interpolated_component {
296 288     288   996 my ($a,$b,$c,$component_min,$component_max,$options,$component_name) = @_;
297             # ranges on the component
298 288         423 my ($min,$max) = ($component_min,$component_max);
299             # ranges on the difference
300 288         301 my ($dmin,$dmax);
301 288 100       597 if(defined $options) {
302 144 50       460 if(ref($options) eq "HASH") {
303 144 50       437 $min = $options->{min} if defined $options->{min};
304 144 50       360 $max = $options->{max} if defined $options->{max};
305 144 50       407 $dmin = $options->{dmin} if defined $options->{dmin};
306 144 50       983 $dmax = $options->{dmax} if defined $options->{dmax};
307             } else {
308 0         0 confess "-$component_name option for must be a hash reference, e.g. -$component_name=>{dmin=>0,dmax=>1}";
309             }
310             }
311 288 50       846 if($min < min($component_min,$component_max)) {
312 0         0 confess "$component_name minimum must be ".min($component_min,$component_max);
313             }
314 288 50       6207 if($max > max($component_min,$component_max)) {
315 0         0 confess "$component_name maximum must be ".max($component_min,$component_max);
316             }
317 288         5259 my $t; # this is the interpolation parameter 0..1
318 288 100 66     1124 if(! defined $dmin || ! defined $dmax) {
319 144         190 $t = 0;
320             } else {
321 144 100 100     991 if($a == $b && $b == $c) {
    50 33        
322 16         24 $t = 0;
323             }
324             elsif(defined $dmin && defined $dmax) {
325 128         346 my $d = _get_maxdiff($a,$b,$c);
326 128 50 33     2643 if(defined $options && $options->{relative}) {
327 0         0 my $rel_factor = max(abs($a),abs($b),abs($c),$d);
328 0 0       0 if($rel_factor) {
329 0         0 $d /= $rel_factor;
330             } else {
331             # this should never happen because a=b=c=0 test
332             # has been done above
333 0         0 $d = 0;
334             }
335             }
336 128 50       390 if($d <= $dmin) {
    100          
337 0         0 $t = 0;
338             } elsif ($d >= $dmax) {
339 36         78 $t = 1;
340             } else {
341 92         240 $t = ($d-$dmin)/($dmax-$dmin);
342             }
343             }
344             else {
345 0         0 $t = 0;
346             }
347             }
348             ## $v
349             ## $vmin
350             ## $vmax
351 288         849 my $component = _interpolate($t,$min,$max);
352 288         2730 return $component;
353             }
354              
355             =for comment
356             Interpolate value (0..1) between max and min
357              
358             =cut
359              
360             sub _interpolate {
361 288     288   419 my ($x,$min,$max) = @_;
362            
363             #my $min_real = $min < $max ? $min : $max;
364             #my $max_real = $max > $min ? $max : $min;
365              
366             ## $x
367             ## $min
368             ## $max
369              
370 288 100       719 if($x <= 0) {
    100          
371 160         321 return $min;
372             }
373             elsif ($x >= 1) {
374 36         74 return $max;
375             }
376             else {
377 92         211 my $d = $max - $min;
378 92         115 $d = $d * $x;
379 92         132 $d = $min + $d;
380 92         170 my $xi = $min + $x * ($max-$min);
381 92         279 return $xi;
382             }
383             }
384              
385             =for comment
386             Retrieve largest difference
387              
388             =cut
389              
390             sub _get_maxdiff {
391 128     128   356 my ($a,$b,$c) = @_;
392 128         797 return scalar max(abs($a-$b),abs($a-$c),abs($b-$c));
393             }
394              
395             =for comment
396             Returns the tuple size for this encoding.
397              
398             =cut
399              
400             sub _get_tuple_size {
401 230     230   759 return $TUPLE_SIZE;
402             }
403              
404             =for comment
405             Returns a list of options that this implementation understands.
406              
407             =cut
408              
409             sub _get_ok_options {
410 2606     2606   8473 return @OPTIONS_OK;
411             }
412              
413             =for comment
414             Returns a hash of default options for this implementation
415              
416             =cut
417              
418             sub _get_default_options {
419 256     256   1125 return %OPTIONS_DEFAULT;
420             }
421              
422             =pod
423              
424             =head1 IMPLEMENTING AN ENCODING CLASS
425              
426             The encoding class must implement the following functions. Given a C object C<$obj>,
427              
428             =head2 C<$value = _get_value( $obj )>
429              
430             =head2 C<$saturation = _get_saturation( $obj )>
431              
432             =head2 C<$hue = _get_hue( $obj )>
433              
434             =head2 C<$size = _get_tuple_size()>
435              
436             =head2 C<@opt_ok =_get_ok_options()>
437              
438             =head2 C<%opt_def = _get_default_options()>
439              
440             =head1 AUTHOR
441              
442             Martin Krzywinski, C<< >>
443              
444             =head1 BUGS
445              
446             Please report any bugs or feature requests to C, or through
447             the web interface at L. I will be notified, and then you'll
448             automatically be notified of progress on your bug as I make changes.
449              
450             =head1 SUPPORT
451              
452             You can find documentation for this module with the perldoc command.
453              
454             perldoc Color::TupleEncode
455              
456             You can also look for information at:
457              
458             =over 4
459              
460             =item * RT: CPAN's request tracker
461              
462             L
463              
464             =item * AnnoCPAN: Annotated CPAN documentation
465              
466             L
467              
468             =item * CPAN Ratings
469              
470             L
471              
472             =item * Search CPAN
473              
474             L
475              
476             =back
477              
478             =head1 SEE ALSO
479              
480             For details about the color encoding, see
481              
482             =over
483              
484             =item Color::TupleEncode
485              
486             Driver module. This is the module that provides an API for the color encoding. See L.
487              
488             =item Color::TupleEncode::2Way
489              
490             A utility module that encodes a 2-tuple to a color. See L.
491              
492             =back
493              
494             =head1 LICENSE AND COPYRIGHT
495              
496             Copyright 2010 Martin Krzywinski.
497              
498             This program is free software; you can redistribute it and/or modify it
499             under the terms of either: the GNU General Public License as published
500             by the Free Software Foundation; or the Artistic License.
501              
502             See http://dev.perl.org/licenses/ for more information.
503              
504             =cut
505              
506             1; # End of Color::TupleEncode::Baran