File Coverage

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 module that would provide
55             access to the slow FITS/GIF interface of the separate DSS1/DSS2 surveys.
56              
57             Optionally, L is used if you'd like to use
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, while the URL to the L
72             JPEG endpoint and the L simple identifier query
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, the JPEG image will
90             be saved to that and the function will return an L object, otherwise the
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 lookup to get the C by name
114             (e.g. 'NGC598'). Will be disregarded if C and C are defined. If you have not
115             defined an C and L has one
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, as the default
124             won't be appropriate for many targets and L often
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, or through
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;