#!/usr/bin/perl -w # RCSid $Id: falsecolor.pl,v 2.25 2023/02/01 18:18:10 greg Exp $ use warnings; use strict; use File::Temp qw/ tempdir /; my @palettes = ('def', 'spec', 'pm3d', 'hot', 'eco', 'tbo'); my $mult = 179.0; # Multiplier. Default W/sr/m2 -> cd/m2 my $label = 'cd/m2'; # Units shown in legend my $scale = 1000; # Top of the scale my $scaledigits = 3; # Number of maximum digits for numbers in legend my $decades = 0; # Default is linear mapping my $pal = 'def'; # Palette my $redv = "${pal}_red(v)"; # Mapping functions for R,G,B my $grnv = "${pal}_grn(v)"; my $bluv = "${pal}_blu(v)"; my $ndivs = 8; # Number of lines in legend my $picture = '-'; my $cpict = ''; my $legwidth = 100; # Legend width and height my $legheight = 200; my @overlayWH = (0,0); # Overlay matrix width and height (default = none) my @overlayRect; # Overlay rectangle (left, lower, right, upper) my $haszero = 1; # print 0 in scale for falsecolor images only my $docont = ''; # Contours: -cl, -cb, and -c0 my $doposter = 0; # Posterization: -cp my $doextrem = 0; # Don't mark extrema my $needfile = 0; my $showpal = 0; # Show available colour palettes my @savedARGV = @ARGV; # Save for final header while ($#ARGV >= 0) { $_ = shift; # Options with qualifiers if (m/-lw/) { # Legend width $legwidth = shift; } elsif (m/-lh/) { # Legend height $legheight = shift; } elsif (m/-m/) { # Multiplier $mult = shift; } elsif (m/-spec/) { die("depricated option '-spec'. Please use '-pal spec' instead.\n"); } elsif (m/-s/) { # Scale $scale = shift; if ($scale =~ m/[aA].*/) { $needfile = 1; } } elsif (m/-d/) { # Number of maximum digits for numbers in legend $scaledigits = shift; } elsif (m/-l$/) { # Label $label = shift; } elsif (m/-log/) { # Logarithmic mapping $decades = shift; } elsif (m/-r/) { # Custom palette functions for R,G,B $redv = shift; } elsif (m/-g/) { $grnv = shift; } elsif (m/-b/) { $bluv = shift; } elsif (m/-pal$/) { # Color palette $pal = shift; if (! grep $_ eq $pal, @palettes) { die("invalid palette '$pal'.\n"); } $redv = "${pal}_red(v)"; $grnv = "${pal}_grn(v)"; $bluv = "${pal}_blu(v)"; } elsif (m/-i$/) { # Image for intensity mapping $picture = shift; } elsif (m/-p$/) { # Image for background $cpict = shift; } elsif (m/-ip/ || m/-pi/) { $picture = shift; $cpict = $picture; } elsif (m/-n/) { # Number of contour lines $ndivs = shift; } elsif (m/-odim$/) { # Overlay width and height $overlayWH[0] = shift; $overlayWH[1] = shift; $needfile ||= $overlayWH[0] && $overlayWH[1]; } elsif (m/-orct/) { # Overlay rectangle $overlayRect[0] = shift; $overlayRect[1] = shift; $overlayRect[2] = shift; $overlayRect[3] = shift; # Switches } elsif (m/-cl/) { # Contour lines $docont = 'a'; $haszero = 0; } elsif (m/-cb/) { # Contour bands $docont = 'b'; $haszero = 0; } elsif (m/-cp/) { # Posterize $doposter = 1; $haszero = 0; } elsif (m/-c0/) { # Turn off falsecolor operation $docont = '0'; $legwidth = 0; $legheight = 0; } elsif (m/-palettes/) { # Show all available palettes $scale = 45824; # 256 * 179 $showpal = 1; } elsif (m/-e/) { $doextrem = 1; $needfile = 1; # Oops! Illegal option } else { die("bad option \"$_\"\n"); } } if (($legwidth <= 20) || ($legheight <= 40)) { # Legend is too small to be legible. Don't bother doing one. $legwidth = 0; $legheight = 0; } # Temporary directory. Will be removed upon successful program exit. my $td = tempdir( CLEANUP => 1 ); if ($needfile && $picture eq '-') { # Pretend that $td/stdin.rad is the actual filename. $picture = "$td/stdin.hdr"; open(FHpic, ">$picture") or die("Unable to write to file $picture\n"); # Input is from STDIN: Capture to file. while (<>) { print FHpic; } close(FHpic); if ($cpict eq '-') { $cpict = $picture; } } # Find a good scale for auto mode. if ($scale =~ m/[aA].*/) { my @histo = split(/\s/, `phisto $picture`); # e.g. $ phisto tests/richmond.hdr| tail -2 # 3.91267 14 # 3.94282 6 my $LogLmax = $histo[-4]; $scale = $mult / 179 * 10**$LogLmax; } if ($doposter) { # -cp -> $doposter = 1 my $newv = join( "seg2(v)", split("v", $redv) ); $redv = $newv; $newv = join( "seg2(v)", split("v", $bluv) ); $bluv = $newv; $newv = join( "seg2(v)", split("v", $grnv) ); $grnv = $newv; } elsif ($docont) { # -cl -> $docont = a # -cb -> $docont = b my $newv = join( "(v-1/ndivs)*ndivs/(ndivs-1)", split("v", $redv) ); $redv = $newv; $newv = join( "(v-1/ndivs)*ndivs/(ndivs-1)", split("v", $bluv) ); $bluv = $newv; $newv = join( "(v-1/ndivs)*ndivs/(ndivs-1)", split("v", $grnv) ); $grnv = $newv; } my $pc0 = "$td/pc0.cal"; open(FHpc0, ">$pc0"); print FHpc0 <$pc1"); print FHpc1 < $lbimg"; my $cmd = qq[pcomb $pc0args -e "v=x/256"]; $cmd .= qq[ -e "ro=clip(${pal}_red(v));go=clip(${pal}_grn(v));bo=clip(${pal}_blu(v))"]; $cmd .= qq[ -x 256 -y 30 > $fcimg]; system "$cmd"; $pc .= " $fcimg $lbimg"; } system "$pc"; exit 0; } if ($cpict eq '' && $docont ne '0') { $pc1args .= qq[ -e "ra=0;ga=0;ba=0"]; } elsif ($cpict eq $picture) { $cpict = ''; } # Logarithmic mapping if ($decades > 0) { $pc1args .= qq[ -e "map(x)=if(x-10^-$decades,log10(x)/$decades+1,0)"]; } # Contours if ($docont ne '') { # -cl -> $docont = a # -cb -> $docont = b # -c0 -> $docont = FALSE $pc0args .= qq[ -e "in=iscont$docont"]; } # Colours in the legend my $scolpic = "$td/scol.hdr"; # Labels in the legend my $slabpic = "$td/slab.hdr"; my $cmd; if ($legwidth > 0) { # Legend: Create the text labels my $sheight = int($legheight / $ndivs ); my $theight = int($legwidth/(8/1.67)); my $stheight = $sheight <= $theight ? $sheight : $theight; my $vlegheight = int($sheight * $ndivs * (1+1.5/$ndivs)); my $tslabpic = "$td/slabT.hdr"; system "psign -s -.15 -cf 1 1 1 -cb 0 0 0 -h $stheight $label > $tslabpic"; my $loop = $ndivs+$haszero; my $hlegheight = int($sheight * ($loop) + $sheight * .5); my $pcompost = qq[pcompos -b 0 0 0 =-0 $tslabpic 0 $hlegheight ]; for (my $i=0; $i<$loop; $i++) { my $imap = ($ndivs - $i) / $ndivs; my $value = $scale; if ($decades > 0) { $value *= 10**(($imap - 1) * $decades); } else { $value *= $imap; } # Limit decimal places if ($scaledigits <= 0) { $value =~ s/\.[0-9]*//; } else { $value =~ s/(\.[0-9]{$scaledigits})[0-9]*/$1/; } $tslabpic = "$td/slab$i.hdr"; system "psign -s -.15 -cf 1 1 1 -cb 0 0 0 -h $stheight $value > $tslabpic"; $hlegheight = int($sheight * ($loop - $i - 1) + $sheight * .5); $pcompost .= qq[=-0 $tslabpic 0 $hlegheight ]; } $pcompost .= qq[ > $slabpic]; system $pcompost; # Legend: Create the background colours $cmd = qq[pcomb $pc0args]; $cmd .= qq[ -x $legwidth -y $vlegheight]; $cmd .= qq[ -e "v=(y+.5-$sheight)/(yres/(1+1.5/$ndivs));vleft=v;vright=v"]; $cmd .= qq[ -e "vbelow=(y-.5-$sheight)/(yres/(1+1.5/$ndivs));vabove=(y+1.5-$sheight)/(yres/(1+1.5/$ndivs))"]; $cmd .= qq[ -e "ra=0;ga=0;ba=0;"]; $cmd .= qq[ > $scolpic]; system $cmd; } else { # Create dummy colour scale and legend labels so we don't # need to change the final command line. system "pcomb -x 1 -y 1 -e lo=1 > $scolpic"; system "pcomb -x 1 -y 1 -e lo=1 > $slabpic"; } # Legend: Invert the text labels (for dropshadow) my $slabinvpic = "$td/slabinv.hdr"; system qq[pcomb -e "lo=1-gi(1)" $slabpic > $slabinvpic]; my $sh0 = -int($legheight / $ndivs / 2); if ($haszero < 1) { $sh0 = -int($legheight / ($ndivs)*1.5); } # Command line without extrema $cmd = qq[pcomb $pc0args $pc1args "$picture"]; $cmd .= qq[ "$cpict"] if ($cpict); $cmd .= qq[ | pcompos -h -b 0 0 0 $scolpic 0 $sh0 +t .1 $slabinvpic 2 -1]; $cmd .= qq[ -t .5 $slabpic 0 0 - $legwidth 0]; my $cheight = 32; if ($overlayWH[0] && $overlayWH[1]) { # Overlay picture matrix values my @picWH = split ' ', `getinfo -d < $picture`; @picWH = ($picWH[3], $picWH[1]); if ($#overlayRect != 3) { @overlayRect = (0, 0, @picWH); } elsif ($overlayRect[2] <= $overlayRect[0] || $overlayRect[3] <= $overlayRect[1]) { die("Illegal overlay rectangle\n"); } # Compute spacing between values my @cropWH = ($overlayRect[2]-$overlayRect[0], $overlayRect[3]-$overlayRect[1]); my $ohspacing = $cropWH[0] / $overlayWH[0]; my $ovspacing = $cropWH[1] / $overlayWH[1]; # Compute character height from spacing $cheight = int($ovspacing * 0.67 + 0.5); if ($cheight >= 1.67/10 * $ohspacing) { $cheight = int(1.67/10 * $ohspacing); } if ($cheight < 10) { die "Overlay matrix spacing too tight\n"; } my $cmd1 = qq[pcompos -x $cropWH[0] -y $cropWH[1] "$picture"] . qq[ -$overlayRect[0] -$overlayRect[1]]; $cmd1 .= qq[ | pfilt -1 -b -x /$cheight -y /$cheight]; $cmd1 .= qq[ | pfilt -1 -r .5 -x $cropWH[0] -y $cropWH[1]]; $cmd1 .= qq[ | pvalue -o -h -H -b -d -e $mult]; # Compute matrix label center positions in subimage my @xpos; foreach (0 .. ($overlayWH[0]-1)) { $xpos[$_] = int($ohspacing * ($_ + 0.5)); } my @ypos; foreach (0 .. ($overlayWH[1]-1)) { $ypos[$_] = int($ovspacing * ($_ + 0.5)); } open(FHsamp, "$cmd1 |"); $cmd1 = qq[pcompos -h -b 0 0 0 -x $cropWH[0] -y $cropWH[1]]; my $pscmd = qq[psign -s -.15 -cf 1 1 1 -cb 0 0 0 -h $cheight]; for (my $y = 0; $y < $cropWH[1]; $y++) { my $ymatch = grep /^$y$/, @ypos; my $cmd2 = qq[pcompos -b 0 0 0 -x $cropWH[0]]; for (my $x = 0; $x < $cropWH[0]; $x++) { my $sampv = ; next if (! $ymatch); next if (! grep /^$x$/, @xpos); chomp $sampv; # Reformatting assumes %e from pvalue $sampv =~ /^\s*([0-9])\.([0-9]+)[eE]([-+]?[0-9]+)$/; my $manti = $1; my $mantf = $2; my $expi = $3; if ($expi < -3) { # use exponent format } elsif ($expi < 0) { my $pref = '0.'; for (my $i = $expi; ++$i < 0; ) { $pref .= '0'; } $sampv = $pref . $manti . $mantf; } elsif ($expi < length($mantf)) { $sampv = sprintf("%g", $sampv); } elsif ($expi <= 6) { $sampv = $manti . $mantf; for (my $i = $expi - length($mantf); $i-- > 0; ) { $sampv .= '0'; } } # else use exponent format $cmd2 .= qq[ =0- "!$pscmd $sampv" $x 0]; } next if (! $ymatch); my $rowpic = "$td/mrow$y.hdr"; system "$cmd2 > $rowpic"; $cmd1 .= qq[ =-0 $rowpic 0 ] . ($cropWH[1]-1 - $y); } close(FHsamp); my $overpic = "$td/overlay.hdr"; system "$cmd1 > $overpic"; my $overinvpic = "$td/overinv.hdr"; system qq[pcomb -e "lo=1-gi(1)" $overpic > $overinvpic]; my $xleft = $legwidth + $overlayRect[0]; my $ybottom = $overlayRect[1]; $cmd .= qq[ +t .1 $overinvpic $xleft $ybottom]; # Offset from drop shadow $xleft -= 2; $ybottom += 1; $cmd .= qq[ -t .5 $overpic $xleft $ybottom]; } if ($doextrem) { # Get min/max image luminance my $cmd1 = 'pextrem -o ' . $picture; my $retval = `$cmd1`; # e.g. # x y r g b # 193 207 3.070068e-02 3.118896e-02 1.995850e-02 # 211 202 1.292969e+00 1.308594e+00 1.300781e+00 my @extrema = split(/\s/, $retval); my ($lxmin, $ymin, $rmin, $gmin, $bmin, $lxmax, $ymax, $rmax, $gmax, $bmax) = @extrema; $lxmin += $legwidth; $lxmax += $legwidth; # Weighted average of R,G,B my $minval = ($rmin * .27 + $gmin * .67 + $bmin * .06) * $mult; my $maxval = ($rmax * .27 + $gmax * .67 + $bmax * .06) * $mult; if ($scaledigits <= 0) { $minval =~ s/\.[0-9]*//; $maxval =~ s/\.[0-9]*//; } else { $minval =~ s/(\.[0-9]{$scaledigits})[0-9]*/$1/; $maxval =~ s/(\.[0-9]{$scaledigits})[0-9]*/$1/; } # Create the labels for min/max intensity my $minvpic = "$td/minv.hdr"; system "psign -s -.15 -a 2 -h $cheight $minval > $minvpic"; my $maxvpic = "$td/maxv.hdr"; system "psign -s -.15 -a 2 -h $cheight $maxval > $maxvpic"; # Add extrema labels to command line $cmd .= qq[ =00 $minvpic $lxmin $ymin =00 $maxvpic $lxmax $ymax]; } # Clean up and simplify info header without constituent commands $cmd .= qq[ | getinfo -r "EXPOSURE" "pcompos " "falsecolor @savedARGV"]; # Process image and combine with legend system $cmd; #EOF