001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.image.dataset;
031
032import java.io.IOException;
033import java.io.InputStream;
034import java.net.URL;
035import java.util.ArrayList;
036import java.util.Iterator;
037import java.util.List;
038import java.util.regex.Matcher;
039import java.util.regex.Pattern;
040
041import javax.xml.parsers.ParserConfigurationException;
042
043import org.openimaj.data.dataset.ReadableListDataset;
044import org.openimaj.image.Image;
045import org.openimaj.io.HttpUtils;
046import org.openimaj.io.InputStreamObjectReader;
047import org.openimaj.util.api.auth.common.FlickrAPIToken;
048
049import com.flickr4java.flickr.Flickr;
050import com.flickr4java.flickr.REST;
051import com.flickr4java.flickr.collections.Collection;
052import com.flickr4java.flickr.collections.CollectionsInterface;
053import com.flickr4java.flickr.galleries.Gallery;
054import com.flickr4java.flickr.photos.Extras;
055import com.flickr4java.flickr.photos.Photo;
056import com.flickr4java.flickr.photos.PhotoList;
057import com.flickr4java.flickr.photosets.PhotosetsInterface;
058
059/**
060 * Class to dynamically create image datasets from flickr through various api
061 * calls.
062 * 
063 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
064 * 
065 * @param <IMAGE>
066 *            The type of {@link Image} instance held by the dataset.
067 */
068public class FlickrImageDataset<IMAGE extends Image<?, IMAGE>> extends ReadableListDataset<IMAGE, InputStream> {
069        /**
070         * Possible sizes of image from flickr.
071         * 
072         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
073         */
074        public enum Size {
075                /**
076                 * The original uploaded size
077                 */
078                Original {
079                        @Override
080                        protected URL getURL(Photo photo) {
081                                try {
082                                        return new URL(photo.getOriginalUrl());
083                                } catch (final Exception e) {
084                                        throw new RuntimeException(e);
085                                }
086                        }
087                },
088                /**
089                 * Large size
090                 */
091                Large {
092                        @Override
093                        protected URL getURL(Photo photo) {
094                                try {
095                                        return new URL(photo.getLargeUrl());
096                                } catch (final Exception e) {
097                                        throw new RuntimeException(e);
098                                }
099                        }
100                },
101                /**
102                 * Medium size
103                 */
104                Medium {
105                        @Override
106                        protected URL getURL(Photo photo) {
107                                try {
108                                        return new URL(photo.getMediumUrl());
109                                } catch (final Exception e) {
110                                        throw new RuntimeException(e);
111                                }
112                        }
113                },
114                /**
115                 * Small size
116                 */
117                Small {
118                        @Override
119                        protected URL getURL(Photo photo) {
120                                try {
121                                        return new URL(photo.getSmallUrl());
122                                } catch (final Exception e) {
123                                        throw new RuntimeException(e);
124                                }
125                        }
126                },
127                /**
128                 * Thumbnail size
129                 */
130                Thumbnail {
131                        @Override
132                        protected URL getURL(Photo photo) {
133                                try {
134                                        return new URL(photo.getThumbnailUrl());
135                                } catch (final Exception e) {
136                                        throw new RuntimeException(e);
137                                }
138                        }
139                },
140                /**
141                 * Square thumbnail size
142                 */
143                Square {
144                        @Override
145                        protected URL getURL(Photo photo) {
146                                try {
147                                        return new URL(photo.getSmallSquareUrl());
148                                } catch (final Exception e) {
149                                        throw new RuntimeException(e);
150                                }
151                        }
152                };
153
154                protected abstract URL getURL(Photo photo);
155        }
156
157        private final static Pattern GALLERY_URL_PATTERN = Pattern.compile(".*/photos/.*/galleries/[0-9]*(/|$)");
158        private final static Pattern PHOTOSET_URL_PATTERN = Pattern.compile(".*/photos/.*/sets/([0-9]*)(/|$)");
159        private final static Pattern COLLECTION_URL_PATTERN = Pattern.compile(".*/photos/(.*)/collections/([0-9]*)(/|$)");
160
161        protected List<Photo> photos;
162        protected Size targetSize = Size.Medium;
163
164        protected FlickrImageDataset(InputStreamObjectReader<IMAGE> reader, List<Photo> photos) {
165                super(reader);
166
167                this.photos = photos;
168        }
169
170        /**
171         * Set the size of the images that this dataset produces.
172         * 
173         * @param size
174         *            the size
175         */
176        public void setImageSize(Size size) {
177                this.targetSize = size;
178        }
179
180        /**
181         * Get the size of the images that this dataset produces.
182         * 
183         * @return the size of the returned images
184         */
185        public Size getImageSize() {
186                return targetSize;
187        }
188
189        /**
190         * Get the underlying flickr {@link Photo} objects.
191         * 
192         * @return the underlying list of {@link Photo}s.
193         */
194        public List<Photo> getPhotos() {
195                return photos;
196        }
197
198        /**
199         * Get the a specific underlying flickr {@link Photo} object corresponding
200         * to a particular image instance.
201         * 
202         * @param index
203         *            the index of the instance
204         * 
205         * @return the underlying {@link Photo} corresponding to the given instance
206         *         index.
207         */
208        public Photo getPhoto(int index) {
209                return photos.get(index);
210        }
211
212        @Override
213        public IMAGE getInstance(int index) {
214                return read(photos.get(index));
215        }
216
217        @Override
218        public int numInstances() {
219                return photos.size();
220        }
221
222        @Override
223        public String getID(int index) {
224                return targetSize.getURL(photos.get(index)).toString();
225        }
226
227        private IMAGE read(Photo next) {
228                if (next == null)
229                        return null;
230
231                InputStream stream = null;
232                try {
233                        stream = HttpUtils.readURL(targetSize.getURL(next));
234
235                        return reader.read(stream);
236                } catch (final IOException e) {
237                        throw new RuntimeException(e);
238                } finally {
239                        try {
240                                if (stream != null)
241                                        stream.close();
242                        } catch (final IOException e) {
243                                // ignore
244                        }
245                }
246        }
247
248        @Override
249        public Iterator<IMAGE> iterator() {
250                return new Iterator<IMAGE>() {
251                        Iterator<Photo> internal = photos.iterator();
252
253                        @Override
254                        public boolean hasNext() {
255                                return internal.hasNext();
256                        }
257
258                        @Override
259                        public IMAGE next() {
260                                return read(internal.next());
261                        }
262
263                        @Override
264                        public void remove() {
265                                internal.remove();
266                        }
267                };
268        }
269
270        @Override
271        public String toString() {
272                return String.format("%s(%d images)", this.getClass().getName(), this.photos.size());
273        }
274
275        /**
276         * Create an image dataset from the flickr gallery, photoset or collection
277         * at the given url.
278         * 
279         * @param reader
280         *            the reader with which to load the images
281         * @param token
282         *            the flickr api authentication token
283         * @param url
284         *            the url of the collection/gallery/photo-set
285         * @return a {@link FlickrImageDataset} created from the given url
286         * @throws Exception
287         *             if an error occurs
288         */
289        public static <IMAGE extends Image<?, IMAGE>> FlickrImageDataset<IMAGE> create(InputStreamObjectReader<IMAGE> reader,
290                        FlickrAPIToken token,
291                        URL url) throws Exception
292        {
293                return create(reader, token, url, 0);
294        }
295
296        /**
297         * Create an image dataset by searching flickr with the given search terms.
298         * 
299         * @param reader
300         *            the reader with which to load the images
301         * @param token
302         *            the flickr api authentication token
303         * @param searchTerms
304         *            the search terms; space separated. Prepending a term with a
305         *            "-" means that the term should not appear.
306         * @return a {@link FlickrImageDataset} created from the given url
307         * @throws Exception
308         *             if an error occurs
309         */
310        public static <IMAGE extends Image<?, IMAGE>> FlickrImageDataset<IMAGE> create(InputStreamObjectReader<IMAGE> reader,
311                        FlickrAPIToken token, String searchTerms) throws Exception
312        {
313                return create(reader, token, searchTerms, 0);
314        }
315
316        /**
317         * Create an image dataset by searching flickr with the given search terms.
318         * The number of images can be limited to a subset.
319         * 
320         * @param reader
321         *            the reader with which to load the images
322         * @param token
323         *            the flickr api authentication token
324         * @param searchTerms
325         *            the search terms; space separated. Prepending a term with a
326         *            "-" means that the term should not appear.
327         * @param number
328         *            the maximum number of images to add to the dataset. Setting to
329         *            0 or less will attempt to use all the images.
330         * @return a {@link FlickrImageDataset} created from the given url
331         * @throws Exception
332         *             if an error occurs
333         */
334        public static <IMAGE extends Image<?, IMAGE>> FlickrImageDataset<IMAGE> create(InputStreamObjectReader<IMAGE> reader,
335                        FlickrAPIToken token,
336                        String searchTerms, int number) throws Exception
337        {
338                final com.flickr4java.flickr.photos.SearchParameters params = new com.flickr4java.flickr.photos.SearchParameters();
339                params.setText(searchTerms);
340
341                return createFromSearch(reader, token, params, number);
342        }
343
344        /**
345         * Create an image dataset from the flickr gallery, photoset or collection
346         * at the given url. The number of images can be limited to a subset.
347         * 
348         * @param reader
349         *            the reader with which to load the images
350         * @param token
351         *            the flickr api authentication token
352         * @param url
353         *            the url of the collection/gallery/photo-set
354         * @param number
355         *            the maximum number of images to add to the dataset. Setting to
356         *            0 or less will attempt to use all the images.
357         * @return a {@link FlickrImageDataset} created from the given url
358         * @throws Exception
359         *             if an error occurs
360         */
361        public static <IMAGE extends Image<?, IMAGE>> FlickrImageDataset<IMAGE> create(InputStreamObjectReader<IMAGE> reader,
362                        FlickrAPIToken token,
363                        URL url, int number) throws Exception
364        {
365                final String urlString = url.toString();
366
367                if (GALLERY_URL_PATTERN.matcher(urlString).matches()) {
368                        return fromGallery(reader, token, urlString, number);
369                } else if (PHOTOSET_URL_PATTERN.matcher(urlString).matches()) {
370                        return fromPhotoset(reader, token, urlString, number);
371                } else if (COLLECTION_URL_PATTERN.matcher(urlString).matches()) {
372                        return fromCollection(reader, token, urlString, number);
373                }
374
375                throw new IllegalArgumentException("Unknown URL type " + urlString);
376        }
377
378        private static <IMAGE extends Image<?, IMAGE>> FlickrImageDataset<IMAGE> fromGallery(
379                        InputStreamObjectReader<IMAGE> reader,
380                        FlickrAPIToken token,
381                        String urlString, int number) throws Exception
382        {
383                final Flickr flickr = makeFlickr(token);
384
385                final Gallery gallery = flickr.getUrlsInterface().lookupGallery(urlString);
386
387                return createFromGallery(reader, token, gallery, number);
388        }
389
390        private static <IMAGE extends Image<?, IMAGE>> FlickrImageDataset<IMAGE> fromPhotoset(
391                        InputStreamObjectReader<IMAGE> reader,
392                        FlickrAPIToken token,
393                        String urlString, int number) throws Exception
394        {
395                final Matcher matcher = PHOTOSET_URL_PATTERN.matcher(urlString);
396                matcher.find();
397                final String setId = matcher.group(1);
398
399                return createFromPhotoset(reader, token, setId, number);
400        }
401
402        private static <IMAGE extends Image<?, IMAGE>> FlickrImageDataset<IMAGE> fromCollection(
403                        InputStreamObjectReader<IMAGE> reader,
404                        FlickrAPIToken token,
405                        String urlString, int number) throws Exception
406        {
407                final Matcher matcher = COLLECTION_URL_PATTERN.matcher(urlString);
408                matcher.find();
409                final String userId = matcher.group(1);
410                final String collectionsId = matcher.group(2);
411
412                return createFromCollection(reader, token, collectionsId, userId, number);
413        }
414
415        /**
416         * Create an image dataset from a flickr gallery with the specified
417         * parameters.
418         * 
419         * @param reader
420         *            the reader with which to load the images
421         * @param token
422         *            the flickr api authentication token
423         * @param gallery
424         *            the gallery.
425         * @return a {@link FlickrImageDataset} created from the gallery described
426         *         by the given parameters
427         * @throws Exception
428         *             if an error occurs
429         */
430        public static <IMAGE extends Image<?, IMAGE>> FlickrImageDataset<IMAGE> createFromGallery(
431                        InputStreamObjectReader<IMAGE> reader,
432                        FlickrAPIToken token,
433                        Gallery gallery) throws Exception
434        {
435                return createFromGallery(reader, token, gallery.getId(), 0);
436        }
437
438        /**
439         * Create an image dataset from a flickr gallery with the specified
440         * parameters.
441         * 
442         * @param reader
443         *            the reader with which to load the images
444         * @param token
445         *            the flickr api authentication token
446         * @param gallery
447         *            the gallery.
448         * @param number
449         *            the maximum number of images to add to the dataset. Setting to
450         *            0 or less will attempt to use all the images.
451         * @return a {@link FlickrImageDataset} created from the gallery described
452         *         by the given parameters
453         * @throws Exception
454         *             if an error occurs
455         */
456        public static <IMAGE extends Image<?, IMAGE>> FlickrImageDataset<IMAGE> createFromGallery(
457                        InputStreamObjectReader<IMAGE> reader,
458                        FlickrAPIToken token,
459                        Gallery gallery,
460                        int number) throws Exception
461        {
462                return createFromGallery(reader, token, gallery.getId(), number);
463        }
464
465        /**
466         * Create an image dataset from a flickr gallery with the specified
467         * parameters.
468         * 
469         * @param reader
470         *            the reader with which to load the images
471         * @param token
472         *            the flickr api authentication token
473         * @param galleryId
474         *            the Flickr gallery ID.
475         * @return a {@link FlickrImageDataset} created from the gallery described
476         *         by the given parameters
477         * @throws Exception
478         *             if an error occurs
479         */
480        public static <IMAGE extends Image<?, IMAGE>> FlickrImageDataset<IMAGE> createFromGallery(
481                        InputStreamObjectReader<IMAGE> reader,
482                        FlickrAPIToken token,
483                        String galleryId) throws Exception
484        {
485                return createFromGallery(reader, token, galleryId, 0);
486        }
487
488        /**
489         * Create an image dataset from a flickr gallery with the specified
490         * parameters. The number of images can be limited to a subset.
491         * 
492         * @param reader
493         *            the reader with which to load the images
494         * @param token
495         *            the flickr api authentication token
496         * @param galleryId
497         *            the Flickr gallery ID
498         * @param number
499         *            the maximum number of images to add to the dataset. Setting to
500         *            0 or less will attempt to use all the images.
501         * @return a {@link FlickrImageDataset} created from the gallery described
502         *         by the given parameters
503         * @throws Exception
504         *             if an error occurs
505         */
506        public static <IMAGE extends Image<?, IMAGE>> FlickrImageDataset<IMAGE> createFromGallery(
507                        InputStreamObjectReader<IMAGE> reader,
508                        FlickrAPIToken token,
509                        String galleryId, int number) throws Exception
510        {
511                final Flickr flickr = makeFlickr(token);
512
513                List<Photo> photos = new ArrayList<Photo>();
514                final PhotoList<Photo> first = flickr.getGalleriesInterface().getPhotos(galleryId, Extras.ALL_EXTRAS, 250, 0);
515                photos.addAll(first);
516
517                if (number > 0)
518                        number = Math.min(number, first.getTotal());
519
520                for (int page = 1, n = photos.size(); n < number; page++) {
521                        final PhotoList<Photo> result = flickr.getGalleriesInterface().getPhotos(galleryId, Extras.ALL_EXTRAS, 250,
522                                        page);
523                        photos.addAll(result);
524                        n += result.size();
525                }
526
527                if (number > 0 && number < photos.size())
528                        photos = photos.subList(0, number);
529
530                return new FlickrImageDataset<IMAGE>(reader, photos);
531        }
532
533        /**
534         * Create an image dataset from a flickr photoset.
535         * 
536         * @param reader
537         *            the reader with which to load the images
538         * @param token
539         *            the flickr api authentication token
540         * @param setId
541         *            the photoset identifier
542         * @return a {@link FlickrImageDataset} created from the gallery described
543         *         by the given parameters
544         * @throws Exception
545         *             if an error occurs
546         */
547        public static <IMAGE extends Image<?, IMAGE>> FlickrImageDataset<IMAGE> createFromPhotoset(
548                        InputStreamObjectReader<IMAGE> reader, FlickrAPIToken token, String setId) throws Exception
549        {
550                return createFromPhotoset(reader, token, setId, 0);
551        }
552
553        /**
554         * Create an image dataset from a flickr photoset. The number of images can
555         * be limited to a subset.
556         * 
557         * @param reader
558         *            the reader with which to load the images
559         * @param token
560         *            the flickr api authentication token
561         * @param setId
562         *            the photoset identifier
563         * @param number
564         *            the maximum number of images to add to the dataset. Setting to
565         *            0 or less will attempt to use all the images.
566         * @return a {@link FlickrImageDataset} created from the gallery described
567         *         by the given parameters
568         * @throws Exception
569         *             if an error occurs
570         */
571        public static <IMAGE extends Image<?, IMAGE>> FlickrImageDataset<IMAGE> createFromPhotoset(
572                        InputStreamObjectReader<IMAGE> reader,
573                        FlickrAPIToken token,
574                        String setId, int number) throws Exception
575        {
576                final Flickr flickr = makeFlickr(token);
577
578                final PhotosetsInterface setsInterface = flickr.getPhotosetsInterface();
579
580                List<Photo> photos = new ArrayList<Photo>();
581                final PhotoList<Photo> first = setsInterface.getPhotos(setId, Extras.ALL_EXTRAS, 0, 250, 0);
582                photos.addAll(first);
583
584                if (number > 0)
585                        number = Math.min(number, first.getTotal());
586
587                for (int page = 1, n = photos.size(); n < number; page++) {
588                        final PhotoList<Photo> result = setsInterface.getPhotos(setId, Extras.ALL_EXTRAS, 0, 250, page);
589                        photos.addAll(result);
590                        n += result.size();
591                }
592
593                if (number > 0 && number < photos.size())
594                        photos = photos.subList(0, number);
595
596                return new FlickrImageDataset<IMAGE>(reader, photos);
597        }
598
599        /**
600         * Create an image dataset from a flickr collection with the specified
601         * parameters.
602         * 
603         * @param reader
604         *            the reader with which to load the images
605         * @param token
606         *            the flickr api authentication token
607         * @param collectionsId
608         *            the collections ID
609         * @param userId
610         *            the user ID
611         * @return a {@link FlickrImageDataset} created from the gallery described
612         *         by the given parameters
613         * @throws Exception
614         *             if an error occurs
615         */
616        public static <IMAGE extends Image<?, IMAGE>> FlickrImageDataset<IMAGE> createFromCollection(
617                        InputStreamObjectReader<IMAGE> reader,
618                        FlickrAPIToken token,
619                        String collectionsId, String userId) throws Exception
620        {
621                return createFromCollection(reader, token, collectionsId, userId, 0);
622        }
623
624        /**
625         * Create an image dataset from a flickr collection with the specified
626         * parameters. The number of images can be limited to a subset.
627         * 
628         * @param reader
629         *            the reader with which to load the images
630         * @param token
631         *            the flickr api authentication token
632         * @param collectionId
633         *            the collection id
634         * @param userId
635         *            the user id
636         * @param number
637         *            the maximum number of images to add to the dataset. Setting to
638         *            0 or less will attempt to use all the images.
639         * @return a {@link FlickrImageDataset} created from the gallery described
640         *         by the given parameters
641         * @throws Exception
642         *             if an error occurs
643         */
644        public static <IMAGE extends Image<?, IMAGE>> FlickrImageDataset<IMAGE> createFromCollection(
645                        InputStreamObjectReader<IMAGE> reader,
646                        FlickrAPIToken token,
647                        String collectionId, String userId, int number) throws Exception
648        {
649                final Flickr flickr = makeFlickr(token);
650
651                List<Photo> photos = new ArrayList<Photo>();
652                final CollectionsInterface collectionsInterface = flickr.getCollectionsInterface();
653
654                final List<Collection> collections = collectionsInterface.getTree(collectionId, userId);
655                for (final Collection collection : collections)
656                        photos.addAll(collection.getPhotos());
657
658                if (number > 0 && number < photos.size())
659                        photos = photos.subList(0, number);
660
661                return new FlickrImageDataset<IMAGE>(reader, photos);
662        }
663
664        /**
665         * Create an image dataset from a flickr search with the specified
666         * parameters.
667         * 
668         * @param reader
669         *            the reader with which to load the images
670         * @param token
671         *            the flickr api authentication token
672         * @param params
673         *            the parameters describing the gallery and any additional
674         *            constraints.
675         * @return a {@link FlickrImageDataset} created from the gallery described
676         *         by the given parameters
677         * @throws Exception
678         *             if an error occurs
679         */
680        public static <IMAGE extends Image<?, IMAGE>> FlickrImageDataset<IMAGE> createFromSearch(
681                        InputStreamObjectReader<IMAGE> reader,
682                        FlickrAPIToken token,
683                        com.flickr4java.flickr.photos.SearchParameters params) throws Exception
684        {
685                return createFromSearch(reader, token, params, 0);
686        }
687
688        /**
689         * Create an image dataset from a flickr search with the specified
690         * parameters. The number of images can be limited to a subset.
691         * 
692         * @param reader
693         *            the reader with which to load the images
694         * @param token
695         *            the flickr api authentication token
696         * @param params
697         *            the parameters describing the gallery and any additional
698         *            constraints.
699         * @param number
700         *            the maximum number of images to add to the dataset. Setting to
701         *            0 or less will attempt to use all the images.
702         * @return a {@link FlickrImageDataset} created from the gallery described
703         *         by the given parameters
704         * @throws Exception
705         *             if an error occurs
706         */
707        public static <IMAGE extends Image<?, IMAGE>> FlickrImageDataset<IMAGE> createFromSearch(
708                        InputStreamObjectReader<IMAGE> reader,
709                        FlickrAPIToken token,
710                        com.flickr4java.flickr.photos.SearchParameters params, int number) throws Exception
711        {
712                final Flickr flickr = makeFlickr(token);
713
714                params.setExtras(Extras.ALL_EXTRAS);
715
716                List<Photo> photos = new ArrayList<Photo>();
717                final PhotoList<Photo> first = flickr.getPhotosInterface().search(params, 250, 0);
718                photos.addAll(first);
719
720                if (number > 0)
721                        number = Math.min(number, first.getTotal());
722
723                for (int page = 1, n = photos.size(); n < number; page++) {
724                        final PhotoList<Photo> result = flickr.getPhotosInterface().search(params, 250, page);
725                        photos.addAll(result);
726                        n += result.size();
727                }
728
729                if (number > 0 && number < photos.size())
730                        photos = photos.subList(0, number);
731
732                return new FlickrImageDataset<IMAGE>(reader, photos);
733        }
734
735        private static Flickr makeFlickr(FlickrAPIToken token) throws ParserConfigurationException {
736                return new Flickr(token.apikey, token.secret, new REST());
737        }
738}