#
package PDL::Demos::Transform_demo;
use PDL;
use PDL::Graphics::PGPLOT::Window;
use PDL::Transform;
use File::Spec;
PDL::Demos::Routines->import();
sub comment($);
sub act($);
sub output;
sub run {
local($PDL::debug) = 0;
local($PDL::verbose) = 0;
##$ENV{PGPLOT_XW_WIDTH}=0.6;
$ENV{PGPLOT_DEV}=$^O =~ /MSWin32/ ? '/GW' : "/XWIN";
# try and find m51.fits
$d = File::Spec->catdir( "PDL", "Demos" );
$m51path = undef;
foreach my $path ( @INC ) {
my $check = File::Spec->catdir( $path, $d );
if ( -d $check ) { $m51path = $check; last; }
}
barf "Unable to find directory ${m51path} within the perl libraries.\n"
unless defined $m51path;
comment q|
This demo illustrates the PDL::Transform module.
It requires PGPLOT support in PDL and makes use of the image of
M51 kindly provided by the Hubble Heritage group at the
Space Telescope Science Institute.
|;
act q|
# PDL::Transform objects embody coordinate transformations.
use PDL::Transform;
# set up a simple linear scale-and-shift relation
$t = t_linear( Scale=>[2,-1], Post=>[100,0]);
print $t;
|;
act q|
# The simplest way to use PDL::Transform is to transform a set of
# vectors. To do this you use the "apply" method.
# Define a few 2-vectors:
$xy = pdl([[0,1],[1,2],[10,3]]);
print "xy: ", $xy;
# Transform the 2-vectors:
print "Transformed: ", $xy->apply( $t );
|;
act q|
# You can invert and compose transformations with 'x' and '!'.
$u = t_linear( Scale=>10 ); # A new transformation (simple x10 scale)
$xy = pdl([[0,1],[10,3]]); # Two 2-vectors
print "xy: ", $xy;
print "xy': ", $xy->apply( !$t ); # Invert $t from earlier.
print "xy'': ", $xy->apply( $u x !$t ); # Hit the result with $u.
|;
act q|
# PDL::Transform is useful for data resampling, and that's perhaps
# the best way to demonstrate it. First, we do a little bit of prep work:
# Read in an image ($m51path has been set up by this demo to
# contain the location of the file). Transform is designed to
# work well with FITS images that contain WCS scientific coordinate
# information, but works equally well in pixel space.
$m51 = rfits("$m51path/m51.fits",{hdrcpy=>1});
# we use a floating-point version of the image in some of the demos
# to highlight the interpolation schemes. (Note that the FITS
# header gets deep-copied automatically into the new variable).
$m51_fl = $m51->float;
# Define a nice, simple scale-by-3 transformation.
$ts = t_scale(3);
|;
act q|
#### Resampling with ->map and no FITS interpretation works in pixel space.
### Create a PGPLOT window, and display the original image
$dev = $^O =~ /MSWin/i ? '/GW' : '/xw';
$win = pgwin( dev=> $dev, nx=>2, ny=>2, Charsize=>2, J=>1, Size=>[8,6] );
$win->imag( $m51 , { DrawWedge=>0, Title=>"M51" } );
### Grow m51 by a factor of 3; origin is at lower left
# (the "pix" makes the resampling happen in pixel coordinate
# space, ignoring the FITS header)
$win->imag( $m51->map( $ts, {pix=>1} ) );
$win->label_axes("","","M51 grown by 3 (pixel coords)");
### Shrink m51 by a factor of 3; origin still at lower left.
# (You can invert the transform with a leading '!'.)
$win->imag( $m51->map( !$ts, {pix=>1} ) );
$win->label_axes("","","M51 shrunk by 3 (pixel coords)");
|;
act q|
# You can work in scientific space (or any other space) by
# wrapping your main transformation with something that translates
# between the coordinates you want to act in, and the coordinates
# you have. Here, "t_fits" translates between pixels in the data
# and arcminutes in the image plane.
### Clear the panel and start over
$win->panel(4); # (Clear whole window on next plot)
$win->imag( $m51, { Title=>"M51" } );
### Scale in scientific coordinates.
# Here's a way to scale in scientific coordinates:
# wrap our transformation in FITS-header transforms to translate
# the transformation into scientific space.
$win->imag( $m51->map( !$ts->wrap(t_fits($m51)), {pix=>1} ) );
$win->label_axes("","","M51 shrunk 3x (sci. coords)");
|;
act q|
# If you don't specify "pix=>1" then the resampler works in scientific
# FITS coordinates (if the image has a FITS header):
### Scale in scientific coordinates (origin at center of galaxy)
$win->fits_imag( $m51->map( $ts, $m51->hdr ), { Title=>"M51 3x" } );
### Instead of setting up a coordinate transformation you can use the
# implicit FITS header matching. Just tweak the template header:
$tohdr = $m51->hdr_copy;
$tohdr->{CDELT1} /= 3; # Magnify 3x in horiz direction
$tohdr->{CDELT2} /= 3; # Magnify 3x in vert direction
### Resample to match the new FITS header
# (Note that, although the image is scaled exactly the same as before,
# this time the scientific coordinates have scaled too.)
$win->fits_imag( $m51->map( t_identity(), $tohdr ), { Title=>"3x (FITS)" } );
|;
act q|
### The three main resampling methods are "sample", "linear", and "jacobian".
# Sampling is fastest, linear interpolation is better. Jacobian resampling
# is slow but prevents aliasing under skew or reducing transformations.
$win->fits_imag( $m51_fl , {Title=>"M51"} );
$win->fits_imag( $m51_fl->map( $ts, $m51_fl, { method=>"sample" } ),
{Title=>"M51 x3 (sampled)"} );
$win->fits_imag( $m51_fl->map( $ts, $m51_fl, { method=>"linear" } ),
{ Title=>"M51 x3 (interp.)"} );
$win->fits_imag( $m51_fl->map( $ts, $m51_fl, { method=>"jacobian" } ),
{ Title=>"M51 x3 (jacob.)"} );
|;
act q|
### Linear transformations are only the beginning. Here's an example
# using a simple nonlinear transformation: radial coordinate transformation.
### Original image
$win->fits_imag( $m51 ,{Title=>"M51"});
### Radial structure in M51 (linear radial scale; origin at (0,0) by default)
$tu = t_radial( u=>'degree' );
$win->fits_imag( $m51_fl->map($tu), { Title=>"M51 radial (linear)", J=>0});
### Radial structure in M51 (conformal/logarithmic radial scale)
$tu_c = t_radial( r0=>0.1 ); # Y axis 0 is at 0.1 arcmin
$win->panel(3);
$win->fits_imag( $m51_fl->map($tu_c),
{ Title=>"M51 radial (conformal)",
YRange=>[0,4] } );
|;
# NOTE:
# need to 'double protect' the \ in the label_axes()
# since it's being evaluated twice (I think)
#
act q|
#####################
# Wrapping transformations allows you to work in a convenient
# space for what you want to do. Here, we can use a simple
# skew matrix to find (and remove) logarithmic spiral structures in
# the galaxy. The "unspiraled" images shift the spiral arms into
# approximate straight lines.
$sp = 3.14159; # Skew by 3.14159
# Skew matrix
$t_skew = t_linear(pre => [$sp * 130, 0] , matrix => pdl([1,0],[-$sp,1]));
# When put into conformal radial space, the skew turns into 3.14159
# radians per scale height.
$t_untwist = t_wrap($t_skew, $tu_c);
# Press enter to see the result of these transforms...
|;
act q|
##############################
# Note that you can use ->map and ->unmap as either PDL methods
# or transform methods; what to do is clear from context.
# Original image
$win->fits_imag($m51, {Title => "M51"} );
# Skewed
$win->fits_imag( $m51_fl->map( $t_skew ),
{ Title => "M51 skewed by \\\\gp in spatial coords" } );
# Untwisted -- show that m51 has a half-twist per scale height
$win->fits_imag( $m51_fl->map( $t_untwist ),
{ Title => "M51 unspiraled (\\\\gp / r\\\\ds\\\\u)"} );
# Untwisted -- the jacobean method uses variable spatial filtering
# to eliminate spatial artifacts, at significant computational cost
# (This may take some time to complete).
$win->fits_imag( $m51_fl->map( $t_untwist, {m=>jacobean}),
{ Title => "M51 unspiraled (\\\\gp / r\\\\ds\\\\u; antialiased)" } );
|;
$win->close;
act q|
### Native FITS interpretation makes it easy to view your data in
### your preferred coordinate system. Here we zoom in on a 0.2x0.2
### arcmin region of M51, sampling it to 100x100 pixels resolution.
$m51 = float $m51;
$data = $m51->match([100,100],{or=>[[-0.05,0.15],[-0.05,0.15]]});
$s = "M51 closeup ("; $ss=" coords)";
$ps = " (pixels)";
$dev = $^O =~ /MSWin/i ? '/GW' : '/xw';
$w1 = pgwin( dev=> $dev, size=>[4,4], charsize=>1.5, justify=>1 );
$w1->imag( $data, 600, 750, { title=>"${s}pixel${ss}",
xtitle=>"X$ps", ytitle=>"Y$ps" } );
$w1->hold;
$w2 = pgwin( dev=> $dev, size=>[4,4], charsize=>1.5, justify=>1 );
$w2->fits_imag( $data, 600, 750, { title=>"${s}sci.${ss}", dr=>0 } );
$w2->hold;
# Now please separate the two X windows on your screen, and press ENTER.
###############################
|;
act q|
### Now rotate the image 360 degrees in 10 degree increments.
### The 'match' method resamples $data to the rotated scientific
### coordinate system in $hdr. The "pixel coordinates" window shows
### the resampled data in their new pixel coordinate system.
### The "sci. coordinates" window shows the data remaining fixed in
### scientific space, even though the pixels that represent them are
### moving and rotating.
$hdr = $data->hdr_copy;
for( $rot=0; $rot<=360; $rot += 10 ) {
$hdr->{CROTA2} = $rot;
$d = $data->match($hdr);
$w1->imag( $d, 600, 750 );
$w2->fits_imag($d, 600, 750, {dr=>0});
}
|;
act q|
### You can do the same thing even with nonsquare coordinates.
### Here, we resample the same region in scientific space into a
### 150x50 pixel array.
$data = $m51->match([150,50],{or=>[[-0.05,0.15],[-0.05,0.15]]});
$hdr = $data->hdr_copy;
$w1->release;
$w1->imag( $data, 600, 750, { title=>"${s}pixel${ss}",
xtitle=>"X$ps", ytitle=>"Y$ps", pix=>1 } );
$w1->hold;
for( $rot=0; $rot<=750; $rot += 5 ) {
$hdr->{CROTA2} = $rot;
$d = $data->match($hdr);
$w1->imag($d, 600, 750); $w2->fits_imag($d, 600, 750, {dr=>0});
}
|;
comment q|
This concludes the PDL::Transform demo.
Be sure to check the documentation for PDL::Transform::Cartography,
which contains common perspective and mapping coordinate systems
that are useful for work on the terrestrial and celestial spheres,
as well as other planets &c.
|;
$w1->release; $w1->close; undef $w1;
$w2->release; $w2->close; undef $w2;
undef $win;
}
1;