| blib/lib/Astro/DSS/JPEG.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 69 | 69 | 100.0 |
| branch | 28 | 28 | 100.0 |
| condition | 27 | 27 | 100.0 |
| subroutine | 12 | 12 | 100.0 |
| pod | 2 | 2 | 100.0 |
| total | 138 | 138 | 100.0 |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | package Astro::DSS::JPEG; | ||||||
| 2 | |||||||
| 3 | 2 | 2 | 357174 | use 5.006; | |||
| 2 | 15 | ||||||
| 4 | 2 | 2 | 11 | use strict; | |||
| 2 | 4 | ||||||
| 2 | 39 | ||||||
| 5 | 2 | 2 | 8 | use warnings; | |||
| 2 | 4 | ||||||
| 2 | 59 | ||||||
| 6 | 2 | 2 | 625 | use utf8; | |||
| 2 | 17 | ||||||
| 2 | 13 | ||||||
| 7 | |||||||
| 8 | 2 | 2 | 1437 | use LWP::UserAgent; | |||
| 2 | 92438 | ||||||
| 2 | 71 | ||||||
| 9 | 2 | 2 | 14 | use List::Util 'min'; | |||
| 2 | 6 | ||||||
| 2 | 2368 | ||||||
| 10 | |||||||
| 11 | =encoding utf8 | ||||||
| 12 | |||||||
| 13 | =head1 NAME | ||||||
| 14 | |||||||
| 15 | Astro::DSS::JPEG - Download color JPEG images from the Digitized Sky Survey | ||||||
| 16 | |||||||
| 17 | =head1 VERSION | ||||||
| 18 | |||||||
| 19 | Version 0.02 | ||||||
| 20 | |||||||
| 21 | =cut | ||||||
| 22 | |||||||
| 23 | our $VERSION = '0.02'; | ||||||
| 24 | |||||||
| 25 | |||||||
| 26 | =head1 SYNOPSIS | ||||||
| 27 | |||||||
| 28 | use Astro::DSS::JPEG; | ||||||
| 29 | |||||||
| 30 | my $dss = Astro::DSS::JPEG->new(); | ||||||
| 31 | |||||||
| 32 | #get an object (Triangulum galaxy M33) by name at default 1000x1000, | ||||||
| 33 | #getting coordinates and frame size automatically from SIMBAD | ||||||
| 34 | my $image = $dss->get_image(target => 'M33'); | ||||||
| 35 | |||||||
| 36 | #Save an image directly to file manually specifying coordinates, frame of | ||||||
| 37 | #90x90 arcmin, 2048x2048 pixels | ||||||
| 38 | $dss->get_image( | ||||||
| 39 | ra => '01 33 50.904', | ||||||
| 40 | dec => '+30 39 35.79', | ||||||
| 41 | angular_size => 90, | ||||||
| 42 | pixel_size => 2048, | ||||||
| 43 | filename => $filename | ||||||
| 44 | ); | ||||||
| 45 | |||||||
| 46 | #In one go, save Andromeda Galaxy to andromeda.jpg | ||||||
| 47 | Astro::DSS::JPEG->new->get_image(target=>'Andromeda Galaxy', filename=>'andromeda.jpg'); | ||||||
| 48 | |||||||
| 49 | =head1 DESCRIPTION | ||||||
| 50 | |||||||
| 51 | Astro::DSS::JPEG downloads JPEG images for any location in the sky from the L |
||||||
| 52 | It is meant to be a simple stand alone module to access a fast JPEG-only API that | ||||||
| 53 | provides color composites made from the blue and red DSS surveys. | ||||||
| 54 | In comparison, there is an old/not updated L |
||||||
| 55 | access to the slow FITS/GIF interface of the separate DSS1/DSS2 surveys. | ||||||
| 56 | |||||||
| 57 | Optionally, L |
||||||
| 58 | an object name/id instead of coordinates. | ||||||
| 59 | |||||||
| 60 | =head1 CONSTRUCTOR METHODS | ||||||
| 61 | |||||||
| 62 | =head2 C |
||||||
| 63 | |||||||
| 64 | my $dss = Astro::DSS::JPEG->new( | ||||||
| 65 | dss_url => 'http://gsss.stsci.edu/webservices/dssjpg/dss.svc/GetImage', # Optional | ||||||
| 66 | simbad_url => 'http://simbad.u-strasbg.fr/simbad/sim-id', # Optional | ||||||
| 67 | ua => $ua # Optional | ||||||
| 68 | ); | ||||||
| 69 | |||||||
| 70 | All parameters are optional. The constructor by default creates an L |
||||||
| 71 | but you can pass your own with C |
||||||
| 72 | JPEG endpoint and the L |
||||||
| 73 | interface can be redefined (e.g. if they change in the future) from the above shown defaults. | ||||||
| 74 | |||||||
| 75 | =head1 METHODS | ||||||
| 76 | |||||||
| 77 | =head2 C |
||||||
| 78 | |||||||
| 79 | my $res = $dss->get_image( | ||||||
| 80 | target => $target, # Not used if RA, Dec provided | ||||||
| 81 | ra => $ra, # Right Ascension (in hours of angle) | ||||||
| 82 | dec => $dec, # Declination (in degrees of angle) | ||||||
| 83 | angular_size => 30, # Optional. Frame angular size in arcmin | ||||||
| 84 | pixel_size => 1000, # Optional. Frame size in pixels | ||||||
| 85 | filename => $filename # Optional. Filename to save image to | ||||||
| 86 | ); | ||||||
| 87 | |||||||
| 88 | Fetches an image either resolving the name through L |
||||||
| 89 | or using RA & Dec when they are defined. If you pass a C |
||||||
| 90 | be saved to that and the function will return an L |
||||||
| 91 | image data will be returned as a response. | ||||||
| 92 | |||||||
| 93 | The parameters to pass: | ||||||
| 94 | |||||||
| 95 | =over 4 | ||||||
| 96 | |||||||
| 97 | =item * C |
||||||
| 98 | |||||||
| 99 | Right ascension, from 0-24h of angle. The option is flexible, it will accept a single | ||||||
| 100 | decimal number - e.g. C<2.5> or a string with hours, minutes, secs like C<'2 30 00'> or | ||||||
| 101 | C<'2h30m30s'> etc. Single/double quotes and single/double prime symbols are accepted | ||||||
| 102 | for denoting minute, second in place of a single space which also works. | ||||||
| 103 | |||||||
| 104 | =item * C |
||||||
| 105 | |||||||
| 106 | Declination, from -90 to 90 degrees of angle. The option is flexible, it will accept | ||||||
| 107 | a single decimal number - e.g. C<54.5> or a string with degrees, minutes, secs like | ||||||
| 108 | C<'+54 30 00'> or C<'54°30m30s'> etc. Single/double quotes and single/double prime symbols | ||||||
| 109 | are accepted for denoting minute, second in place of a single space which also works. | ||||||
| 110 | |||||||
| 111 | =item * C |
||||||
| 112 | |||||||
| 113 | Do a L |
||||||
| 114 | (e.g. 'NGC598'). Will be disregarded if C |
||||||
| 115 | defined an C |
||||||
| 116 | defined, it will be used for the image request with an extra 50% for padding. | ||||||
| 117 | |||||||
| 118 | =item * C |
||||||
| 119 | |||||||
| 120 | Define the frame angular size in arcminutes, either as a single number (square) or | ||||||
| 121 | a comma separated string for a rectangle. Default is 0.5 degrees (equivalent to passing | ||||||
| 122 | C<30> or C<'30,30'> or even C<'30x30'>). The aspect ratio will be overriden by C |
||||||
| 123 | If you care about framing, you should definitely define C |
||||||
| 124 | won't be appropriate for many targets and L |
||||||
| 125 | does not have the info (or even has an inappropriate value). Also, large C |
||||||
| 126 | values can make the response slow (or even error out). | ||||||
| 127 | |||||||
| 128 | =item * C |
||||||
| 129 | |||||||
| 130 | Define the frame size in pixels, either as a single number (square) or a comma separated | ||||||
| 131 | string for a rectangle. Default is 1000 pixels (equivalent to passing C<1000> or | ||||||
| 132 | C<'1000,1000'> or even C<'1000x1000'>). Max possible is C<'4096,4096'> total size, | ||||||
| 133 | or 1 pixel / arcsecond resolution (e.g. 30*60 = 1800 pixels for 30 arcminutes), which | ||||||
| 134 | is the full resolution DSS plates were scanned at. | ||||||
| 135 | |||||||
| 136 | =back | ||||||
| 137 | |||||||
| 138 | =head1 NOTES | ||||||
| 139 | |||||||
| 140 | Some artifacts can be seen at the borders of separate "stripes" of the survey and | ||||||
| 141 | also the particular JPEG endpoint used sometimes can leave the corners as plain black | ||||||
| 142 | squares (depending on the selected frame size, as it is to do with the way it does | ||||||
| 143 | segmentation), so if you want to make sure you have a frame with no corner gaps, | ||||||
| 144 | request some more angular size than you want and crop. | ||||||
| 145 | |||||||
| 146 | Note that the module test suite won't actually fetch data from either DSS or SIMBAD. | ||||||
| 147 | This is mainly to ensure it will not fail even if the DSS & SIMBAD endpoints change, | ||||||
| 148 | as you can still use the module by passing the updated urls to the constructor. It | ||||||
| 149 | also avoids unneeded strain to those free services. | ||||||
| 150 | |||||||
| 151 | =cut | ||||||
| 152 | |||||||
| 153 | |||||||
| 154 | sub new { | ||||||
| 155 | 3 | 3 | 1 | 5727 | my ($class, %opts) = @_; | ||
| 156 | |||||||
| 157 | 3 | 7 | my $self = {}; | ||||
| 158 | 3 | 7 | bless($self, $class); | ||||
| 159 | |||||||
| 160 | $self->{dss_url} = $opts{dss_url} | ||||||
| 161 | 3 | 100 | 18 | || 'http://gsss.stsci.edu/webservices/dssjpg/dss.svc/GetImage'; | |||
| 162 | $self->{simbad_url} = $opts{simbad_url} | ||||||
| 163 | 3 | 100 | 14 | || 'http://simbad.u-strasbg.fr/simbad/sim-id'; | |||
| 164 | $self->{ua} = $opts{ua} | ||||||
| 165 | 3 | 100 | 18 | || LWP::UserAgent->new( | |||
| 166 | agent => "perl/Astro::DSS::JPEG/$VERSION", | ||||||
| 167 | ); | ||||||
| 168 | |||||||
| 169 | 3 | 291 | return $self; | ||||
| 170 | } | ||||||
| 171 | |||||||
| 172 | sub get_image { | ||||||
| 173 | 5 | 5 | 1 | 4176 | my ($self, %opts) = @_; | ||
| 174 | 5 | 100 | 100 | 38 | if ($opts{target} && (! defined $opts{ra} || ! defined $opts{dec})) { | ||
| 100 | |||||||
| 175 | 3 | 13 | my $simbad = $self->_get_simbad(%opts); | ||||
| 176 | 1 | 7 | %opts = ( | ||||
| 177 | %opts, | ||||||
| 178 | %$simbad | ||||||
| 179 | ); | ||||||
| 180 | } | ||||||
| 181 | |||||||
| 182 | 3 | 12 | my $res = $self->_get_dss(_process_options(%opts)); | ||||
| 183 | 3 | 100 | 572 | if ($res->is_success) { | |||
| 184 | 2 | 100 | 150 | return $res if $opts{filename}; | |||
| 185 | 1 | 7 | return $res->decoded_content; | ||||
| 186 | } else { | ||||||
| 187 | 1 | 11 | die "*ERROR* Could not access DSS at: ".$res->request->uri | ||||
| 188 | ."\nHTTP Response:\n".$res->status_line; | ||||||
| 189 | } | ||||||
| 190 | } | ||||||
| 191 | |||||||
| 192 | sub _get_dss { | ||||||
| 193 | 3 | 3 | 12 | my ($self, %opts) = @_; | |||
| 194 | |||||||
| 195 | my @request = ( | ||||||
| 196 | $self->{dss_url} . sprintf( | ||||||
| 197 | "?POS=%f,%f&SIZE=%f,%f&ISIZE=%.0f,%.0f", | ||||||
| 198 | $opts{ra} * 15, # Convert to degrees | ||||||
| 199 | $opts{dec}, | ||||||
| 200 | $opts{angular_size} / 60, # Convert to degrees | ||||||
| 201 | $opts{angular_size_y} / 60, # Convert to degrees | ||||||
| 202 | $opts{pixel_size}, | ||||||
| 203 | $opts{pixel_size_y} | ||||||
| 204 | ) | ||||||
| 205 | 3 | 50 | ); | ||||
| 206 | 3 | 100 | 12 | push @request, (':content_file' => $opts{filename}) if $opts{filename}; | |||
| 207 | 3 | 14 | return $self->{ua}->get(@request); | ||||
| 208 | } | ||||||
| 209 | |||||||
| 210 | sub _convert_coordinates { | ||||||
| 211 | 14 | 14 | 4499 | my %opts = @_; | |||
| 212 | 14 | 100 | 100 | 91 | if (defined $opts{ra} && $opts{ra} =~ /([+-]?[0-9.]+)[ h]+([0-9.]+)[ m′']+([0-9.]+)/) { | ||
| 213 | 4 | 31 | $opts{ra} = $1+$2/60+$3/3600; | ||||
| 214 | } | ||||||
| 215 | 14 | 100 | 100 | 59 | if (defined $opts{dec} && $opts{dec} =~ /([+-]?[0-9.]+)[ d°]+([0-9.]+)[ m′']+([0-9.]+)/) { | ||
| 216 | 5 | 100 | 18 | my $sign = $1 < 0 ? -1 : 1; | |||
| 217 | 5 | 23 | $opts{dec} = (abs($1)+$2/60+$3/3600)*$sign; | ||||
| 218 | } | ||||||
| 219 | 14 | 84 | return %opts; | ||||
| 220 | } | ||||||
| 221 | |||||||
| 222 | sub _process_options { | ||||||
| 223 | 8 | 8 | 4819 | my %opts = _convert_coordinates(@_); | |||
| 224 | 8 | 100 | 29 | $opts{angular_size} ||= 30; # 30' Default angular size | |||
| 225 | 8 | 100 | 23 | $opts{pixel_size} ||= 1000; | |||
| 226 | |||||||
| 227 | 8 | 100 | 61 | if ($opts{angular_size} =~ /(\S+)\s*[,xX]\s*(\S+)/) { | |||
| 228 | 4 | 100 | 19 | $opts{angular_size} = $1 || 30; | |||
| 229 | 4 | 13 | $opts{angular_size_y} = $2; | ||||
| 230 | } | ||||||
| 231 | 8 | 100 | 40 | if ($opts{pixel_size} =~ /(\S+)\s*[,xX]\s*(\S+)/) { | |||
| 232 | 3 | 100 | 11 | $opts{pixel_size} = $1 || 1000; | |||
| 233 | 3 | 7 | $opts{pixel_size_y} = $2; | ||||
| 234 | } | ||||||
| 235 | |||||||
| 236 | 8 | 100 | 21 | $opts{angular_size_y} = $opts{angular_size} unless $opts{angular_size_y}; | |||
| 237 | $opts{pixel_size_y} = $opts{pixel_size}*$opts{angular_size_y}/$opts{angular_size} | ||||||
| 238 | 8 | 100 | 26 | unless $opts{pixel_size_y}; | |||
| 239 | |||||||
| 240 | $opts{"pixel$_"} = min 4096, $opts{"pixel$_"}, $opts{"angular$_"}*60 | ||||||
| 241 | 8 | 64 | for qw/_size _size_y/; | ||||
| 242 | |||||||
| 243 | 8 | 64 | return %opts; | ||||
| 244 | } | ||||||
| 245 | |||||||
| 246 | sub _get_simbad { | ||||||
| 247 | 5 | 5 | 836 | my ($self, %opts) = @_; | |||
| 248 | |||||||
| 249 | my $res = $self->{ua}->get( | ||||||
| 250 | $self->{simbad_url}."?output.format=ASCII&Ident=".$opts{target} | ||||||
| 251 | 5 | 27 | ); | ||||
| 252 | 5 | 100 | 8605 | if ($res->is_success) { | |||
| 253 | 3 | 219 | my $simbad = $res->decoded_content; | ||||
| 254 | 3 | 100 | 195 | if ($simbad =~ /\(ICRS,ep=J2000,eq=2000\): (\S+ \S+ \S+)\s+(\S+ \S+ \S+)/) { | |||
| 255 | 2 | 8 | $opts{ra} = $1; | ||||
| 256 | 2 | 5 | $opts{dec} = $2; | ||||
| 257 | } else { | ||||||
| 258 | 1 | 9 | die "*ERROR* Could not parse SIMBAD output:\n$simbad"; | ||||
| 259 | } | ||||||
| 260 | 2 | 100 | 11 | if ($simbad =~ /Angular size:\s*([0-9.]+)/) { # Get only largest dimension | |||
| 261 | 1 | 10 | $opts{angular_size} = min($1 * 1.5, 600); # Get 50% extra for framing, max out at 10 degrees | ||||
| 262 | } | ||||||
| 263 | } else { | ||||||
| 264 | die "*ERROR* Could not access SIMBAD at: " | ||||||
| 265 | .$self->{simbad_url}."?output.format=ASCII&Ident=".$opts{target} | ||||||
| 266 | 2 | 28 | ."\nHTTP response:\n".$res->status_line; | ||||
| 267 | } | ||||||
| 268 | |||||||
| 269 | 2 | 13 | return \%opts; | ||||
| 270 | } | ||||||
| 271 | |||||||
| 272 | =head1 AUTHOR | ||||||
| 273 | |||||||
| 274 | Dimitrios Kechagias, C<< |
||||||
| 275 | |||||||
| 276 | |||||||
| 277 | =head1 BUGS | ||||||
| 278 | |||||||
| 279 | Please report any bugs or feature requests to C |
||||||
| 280 | the web interface at L |
||||||
| 281 | I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. | ||||||
| 282 | |||||||
| 283 | You could also submit issues or even pull requests to the | ||||||
| 284 | github repo (see below). | ||||||
| 285 | |||||||
| 286 | |||||||
| 287 | =head1 GIT | ||||||
| 288 | |||||||
| 289 | L |
||||||
| 290 | |||||||
| 291 | |||||||
| 292 | =head1 ACKNOWLEDGEMENTS | ||||||
| 293 | |||||||
| 294 | The DSS images are downloaded using a public api of the L |
||||||
| 295 | provided by the L |
||||||
| 296 | |||||||
| 297 | Targets by name/id are resolved using the L |
||||||
| 298 | |||||||
| 299 | |||||||
| 300 | =head1 LICENSE AND COPYRIGHT | ||||||
| 301 | |||||||
| 302 | This software is copyright (c) 2020 by Dimitrios Kechagias. | ||||||
| 303 | |||||||
| 304 | This is free software; you can redistribute it and/or modify it under | ||||||
| 305 | the same terms as the Perl 5 programming language system itself. | ||||||
| 306 | |||||||
| 307 | For the images you download with this module, please see the L |
||||||
| 308 | for the full copyright info. | ||||||
| 309 | |||||||
| 310 | |||||||
| 311 | =cut | ||||||
| 312 | |||||||
| 313 | 1; |