# 3.2 - Images - Download pre-downsized images for machine learning¶

I want to download a bunch of small images, already scaled down for my CNN

https://labs.onb.ac.at/en/dataset/akon/

https://labs.onb.ac.at/gitlab/labs-team/raw-metadata/raw/master/akon_postcards_public_domain.csv.bz2

https://github.com/h2non/jsonpath-ng

Let's say you got a bunch of old timey scenery photographs. And you want to extract all images containing mountains, why not. And, because you can, you want an AI to do all the dirty work for you.

What that has to do with this workshop?

You can use the historic postcards from the ONB Labs as training data for your AI.

Disclaimer: The AI-part is beyond the scope of this notebook, and would blow up the size of the venv considerably.

If you want instructions on actually performing the training, take a look at

- Keras or
- PyTorch.

One way to do it: Download a VGG16 network that's pre-trained on ImageNet, remove the last layer (the actual classifier), add your own output layer with 2 outputs ('mountain', 'no mountain') and train that one.

Now back to the show.

What do we have to do?

- Download Metdata
  - List of all available postcards
  - Info about the 'mountain-ness' of postcards
- Create Download Links
  - To fetch all images
- Split Into Two Sets
  - Mountain and non-mountain
- Download Images

## Download Metadata¶

Download the metadata set from the ONB Labs

In [1]:

    import pandas as pd

    # Let pandas show all available columns
    pd.set_option('display.max_columns', 50)
    # Pandas can read data directly from web links, even compressed files
    meta = pd.read_csv('https://labs.onb.ac.at/gitlab/labs-team/' \
                       'raw-metadata/raw/master/akon_postcards_public_domain.csv.bz2', compression='bz2')

    /home/oida/labs/pydays19/venv/lib/python3.7/site-packages/IPython/core/interactiveshell.py:3049: DtypeWarning: Columns (13) have mixed types. Specify dtype option on import or set low_memory=False.
      interactivity=interactivity, compiler=compiler, result=result)

In [2]:

    meta.sample(6)

Out[2]:

| Unnamed: 0 | akon_id | id | altitude | building | city | color | comment | mountain | other | photographer | publisher | publisher_place | region | water_body | year | inventory_number | signature | revision_date | date | feature_class | feature_code | geoname_id | latitude | longitude | name | country_id | admin_name_1 | admin_code_1 | geo
--- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | ---
6683 | 6683 | AK121_352 | 80931 | NaN | Zwinger | Dresden | False | v. 1907 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | Geogr. Topogr. Bilder-Samml. 1943, 7402 | 2014-08-25 13:52:35.479 | vor 1907 | P | PPLA | 2935022.0 | 51.05089 | 13.73832 | Dresden | DE | NaN | NaN | 51.05089, 13.73832
1060 | 1060 | AK074_287 | 45904 | NaN | NaN | Solingen | True | 1908 gel | NaN | Kaiser Wilhelm-Brücke | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 2014-08-19 15:22:42.160 | gelaufen 1908 | P | PPLA3 | 2831580.0 | 51.17343 | 7.08450 | Solingen | DE | Nordrhein-Westfalen | 07 | 51.17343, 7.0845
34225 | 34225 | AK087_169 | 54994 | NaN | NaN | Venezia, Piazza S. Marco | False | 1925 gel | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 2014-08-25 09:26:12.544 | gelaufen 1925 | P | PPLA | 3164603.0 | 45.43713 | 12.33265 | Venecia | IT | NaN | NaN | 45.43713, 12.33265
20250 | 20250 | AK030_367 | 17883 | NaN | NaN | Vorder Stoder | False | NaN | Todtengebirge, Spitzmauer, Kleiner Priel, Groß... | NaN | NaN | Ledermann | Wien | NaN | NaN | 1909.0 | NaN | NaN | 2014-08-04 07:59:10.235 | 1909 | P | PPL | 2762185.0 | 47.71337 | 14.22712 | Vorderstoder | AT | NaN | NaN | 47.71337, 14.22712
19981 | 19981 | AK029_173 | 17088 | NaN | NaN | Pöggstall | False | 1903 gel | NaN | NaN | NaN | Hofmeister | Pöggstall | NaN | NaN | NaN | NaN | NaN | 2014-08-04 07:59:10.223 | gelaufen 1903 | P | PPLA3 | 2768616.0 | 48.31667 | 15.18333 | Pöggstall | AT | NaN | NaN | 48.31667, 15.18333
30492 | 30492 | AK088_055 | 55510 | NaN | NaN | Neutitschein, Obertorstrasse | False | 1920 gel | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 2014-08-28 13:39:02.860 | gelaufen 1920 | P | PPL | 3069305.0 | 49.59438 | 18.01028 | Neutitschein | CZ | NaN | NaN | 49.59438, 18.01028

Ok, we have metadata. And look, there's a column mountain:

In [3]:

    meta.sample(5)[['akon_id', 'mountain']]

Out[3]:

| akon_id | mountain
--- | --- | ---
33148 | AK076_442 | Watzmann, Hochkalter
29503 | AK070_327 | NaN
5663 | AK075_470 | NaN
31604 | AK107_561 | NaN
8748 | AK091_235 | NaN

Later, we'll split the dataset in two using the data in this column.

## Create Download Links¶

The SACHA project provides an API for accessing digitized objects of the National Library via IIIF. The online documentation for the API is here: https://iiif.onb.ac.at/api.

We're especially interested in the possibility to serve manifests: https://iiif.onb.ac.at/api#_manifestrequestprocessor:

    GET /presentation/{projectName}/{id}/manifest

    GET /presentation/{projectName}/{id}/manifest

The `projectName` is `AKON` ('AnsichtsKarten ONline'), the `id` is the `akon_id`.

See also https://iiif.onb.ac.at/api#_digitization_projects.

### Create Download Links for Manifests¶

In [4]:

    def akon_id_to_manifest_link(akon_id):
        return f'https://iiif.onb.ac.at/presentation/AKON/{akon_id}/manifest'

In [5]:

    akon_id_to_manifest_link('AK024_176')

Out[5]:

    'https://iiif.onb.ac.at/presentation/AKON/AK024_176/manifest'

Let's test the link

In [6]:

    import requests

    r = requests.get(akon_id_to_manifest_link('AK024_176'))
    r.json()

Out[6]:

    {'@context': 'https://iiif.io/api/presentation/2/context.json',
     '@id': 'https://iiif.onb.ac.at/presentation/AKON/AK024_176/manifest',
     '@type': 'sc:Manifest',
     'label': 'Wien, III',
     'metadata': [{'label': [{'@value': 'Id', '@language': 'en'},
        {'@value': 'Id', '@language': 'ger'}],
       'value': 'AK024_176'},
      {'label': [{'@value': 'Title', '@language': 'en'},
        {'@value': 'Titel', '@language': 'ger'}],
       'value': 'Wien, III'},
      {'label': [{'@value': 'Place', '@language': 'en'},
        {'@value': 'Ort', '@language': 'ger'}],
       'value': "<a href='https://sws.geonames.org/2773040'>Landstraße</a>"},
      {'label': [{'@value': 'Publisher', '@language': 'en'},
        {'@value': 'Verlag', '@language': 'ger'}],
       'value': 'Ledermann'},
      {'label': [{'@value': 'Place of Publications', '@language': 'en'},
        {'@value': 'Erscheinungsort', '@language': 'ger'}],
       'value': 'Wien'},
      {'label': [{'@value': 'Year', '@language': 'en'},
        {'@value': 'Jahr', '@language': 'ger'}],
       'value': '1906'},
      {'label': [{'@value': 'Disseminator', '@language': 'en'},
        {'@value': 'Anbieter', '@language': 'ger'}],
       'value': "<a href='https://akon.onb.ac.at/'>Ansichtskarten Online</a>"},
      {'label': [{'@value': 'Physical Location', '@language': 'en'},
        {'@value': 'Standort', '@language': 'ger'}],
       'value': 'ÖNB'}],
     'description': 'Russische Kirche',
     'viewingDirection': 'left-to-right',
     'viewingHint': 'paged',
     'license': 'http://creativecommons.org/publicdomain/mark/1.0/',
     'attribution': [{'@value': 'Austrian National Library', '@language': 'en'},
      {'@value': 'Österreichische Nationalbibliothek', '@language': 'ger'}],
     'logo': 'https://iiif.onb.ac.at/logo/',
     'seeAlso': [{'@id': 'http://data.onb.ac.at/AKON/AK024_176',
       'format': 'text/html'},
      {'@id': 'http://data.onb.ac.at/AKON/AK024_176.rdf',
       'format': 'application/rdf+xml'}],
     'sequences': [{'@context': 'https://iiif.io/api/presentation/2/context.json',
       '@id': 'https://iiif.onb.ac.at/presentation/AKON/AK024_176/sequence/normal',
       '@type': 'sc:Sequence',
       'startCanvas': 'https://iiif.onb.ac.at/presentation/AKON/AK024_176/canvas/176',
       'canvases': [{'@context': 'https://iiif.io/api/presentation/2/context.json',
         '@id': 'https://iiif.onb.ac.at/presentation/AKON/AK024_176/canvas/176',
         '@type': 'sc:Canvas',
         'label': 'Wien, III',
         'height': 1681,
         'width': 1082,
         'images': [{'@context': 'https://iiif.io/api/presentation/2/context.json',
           '@id': 'https://iiif.onb.ac.at/presentation/AKON/AK024_176/annotation/176',
           '@type': 'oa:Annotation',
           'motivation': 'sc:painting',
           'resource': {'@id': 'https://iiif.onb.ac.at/images/AKON/AK024_176/176/full/full/0/native.jpg',
            '@type': 'dctypes:Image',
            'height': 1681,
            'width': 1082,
            'format': 'image/jpeg',
            'service': {'@context': 'https://iiif.io/api/image/2/context.json',
             '@id': 'https://iiif.onb.ac.at/images/AKON/AK024_176/176',
             'profile': 'https://iiif.io/api/image/2/level2.json'}},
           'on': 'https://iiif.onb.ac.at/presentation/AKON/AK024_176/canvas/176'}]}]}]}

The manifest link seems to work. Let's add manifest links for all postcards to the dataframe:

In [7]:

    meta['manifest_link'] = meta['akon_id'].apply(akon_id_to_manifest_link)

In [8]:

    meta.sample(6)[['akon_id', 'manifest_link']]

Out[8]:

| akon_id | manifest_link
--- | --- | ---
32242 | AK049_538 | https://iiif.onb.ac.at/presentation/AKON/AK049...
10827 | AK001_237 | https://iiif.onb.ac.at/presentation/AKON/AK001...
14148 | AK009_081 | https://iiif.onb.ac.at/presentation/AKON/AK009...
8074 | AK087_246 | https://iiif.onb.ac.at/presentation/AKON/AK087...
33232 | AK082_006 | https://iiif.onb.ac.at/presentation/AKON/AK082...
22877 | AK040_083 | https://iiif.onb.ac.at/presentation/AKON/AK040...

### Collect Image Links¶

Let's take a look at that manifest again:

In [9]:

    r = requests.get(akon_id_to_manifest_link('AK024_176'))
    r.json()

Out[9]:

    {'@context': 'https://iiif.io/api/presentation/2/context.json',
     '@id': 'https://iiif.onb.ac.at/presentation/AKON/AK024_176/manifest',
     '@type': 'sc:Manifest',
     'label': 'Wien, III',
     'metadata': [{'label': [{'@value': 'Id', '@language': 'en'},
        {'@value': 'Id', '@language': 'ger'}],
       'value': 'AK024_176'},
      {'label': [{'@value': 'Title', '@language': 'en'},
        {'@value': 'Titel', '@language': 'ger'}],
       'value': 'Wien, III'},
      {'label': [{'@value': 'Place', '@language': 'en'},
        {'@value': 'Ort', '@language': 'ger'}],
       'value': "<a href='https://sws.geonames.org/2773040'>Landstraße</a>"},
      {'label': [{'@value': 'Publisher', '@language': 'en'},
        {'@value': 'Verlag', '@language': 'ger'}],
       'value': 'Ledermann'},
      {'label': [{'@value': 'Place of Publications', '@language': 'en'},
        {'@value': 'Erscheinungsort', '@language': 'ger'}],
       'value': 'Wien'},
      {'label': [{'@value': 'Year', '@language': 'en'},
        {'@value': 'Jahr', '@language': 'ger'}],
       'value': '1906'},
      {'label': [{'@value': 'Disseminator', '@language': 'en'},
        {'@value': 'Anbieter', '@language': 'ger'}],
       'value': "<a href='https://akon.onb.ac.at/'>Ansichtskarten Online</a>"},
      {'label': [{'@value': 'Physical Location', '@language': 'en'},
        {'@value': 'Standort', '@language': 'ger'}],
       'value': 'ÖNB'}],
     'description': 'Russische Kirche',
     'viewingDirection': 'left-to-right',
     'viewingHint': 'paged',
     'license': 'http://creativecommons.org/publicdomain/mark/1.0/',
     'attribution': [{'@value': 'Austrian National Library', '@language': 'en'},
      {'@value': 'Österreichische Nationalbibliothek', '@language': 'ger'}],
     'logo': 'https://iiif.onb.ac.at/logo/',
     'seeAlso': [{'@id': 'http://data.onb.ac.at/AKON/AK024_176',
       'format': 'text/html'},
      {'@id': 'http://data.onb.ac.at/AKON/AK024_176.rdf',
       'format': 'application/rdf+xml'}],
     'sequences': [{'@context': 'https://iiif.io/api/presentation/2/context.json',
       '@id': 'https://iiif.onb.ac.at/presentation/AKON/AK024_176/sequence/normal',
       '@type': 'sc:Sequence',
       'startCanvas': 'https://iiif.onb.ac.at/presentation/AKON/AK024_176/canvas/176',
       'canvases': [{'@context': 'https://iiif.io/api/presentation/2/context.json',
         '@id': 'https://iiif.onb.ac.at/presentation/AKON/AK024_176/canvas/176',
         '@type': 'sc:Canvas',
         'label': 'Wien, III',
         'height': 1681,
         'width': 1082,
         'images': [{'@context': 'https://iiif.io/api/presentation/2/context.json',
           '@id': 'https://iiif.onb.ac.at/presentation/AKON/AK024_176/annotation/176',
           '@type': 'oa:Annotation',
           'motivation': 'sc:painting',
           'resource': {'@id': 'https://iiif.onb.ac.at/images/AKON/AK024_176/176/full/full/0/native.jpg',
            '@type': 'dctypes:Image',
            'height': 1681,
            'width': 1082,
            'format': 'image/jpeg',
            'service': {'@context': 'https://iiif.io/api/image/2/context.json',
             '@id': 'https://iiif.onb.ac.at/images/AKON/AK024_176/176',
             'profile': 'https://iiif.io/api/image/2/level2.json'}},
           'on': 'https://iiif.onb.ac.at/presentation/AKON/AK024_176/canvas/176'}]}]}]}

We need to collect all `@id`s from all `resource`s from all `images` from all `canvases`.

That's tedious by hand. We'll use `jsonpath-ng`:

In [10]:

    from jsonpath_ng import jsonpath, parse

    image_id_jp = parse('$.sequences[*].canvases[*].images[*].resource.@id')

In [11]:

    [match.value for match in image_id_jp.find(r.json())]

Out[11]:

    ['https://iiif.onb.ac.at/images/AKON/AK024_176/176/full/full/0/native.jpg']

All of this in one function:

In [12]:

    image_id_jp = parse('$.sequences[*].canvases[*].images[*].resource.@id')

    def image_links_for_manifest_link(manifest_link):
        r = requests.get(manifest_link)
        try:
            json = r.json()
        except:
            # default to empty on exceptions - makes batch processing easier in pandas
            json = {}
        image_links = [match.value for match in image_id_jp.find(json)]
        return image_links

Let's test it:

In [13]:

    random_akon_id = meta.sample().iloc[0]['akon_id']
    manifest_link = akon_id_to_manifest_link(random_akon_id)
    image_links_for_manifest_link(manifest_link)

Out[13]:

    ['https://iiif.onb.ac.at/images/AKON/AK036_284/284/full/full/0/native.jpg']

Looking good.

Now let's add the image links to the dataframe...

...actually, let's not do that now, because it takes a while (upwards of 10 minutes). Let's cheat instead, skip this step and load the resulting dataframe directly.

In [14]:

    # %%time
    # meta['image_links'] = meta['manifest_link'].apply(image_links_for_manifest_link)

In [15]:

    import json

    def load_json(s):
        try:
            return json.loads(s.replace("'", '"'))
        except:
            return []

    meta = pd.read_csv('postcards_with_image_links.csv.bz2', compression='bz2', converters={
        'image_links': load_json
    })

    /home/oida/labs/pydays19/venv/lib/python3.7/site-packages/IPython/core/interactiveshell.py:3049: DtypeWarning: Columns (14) have mixed types. Specify dtype option on import or set low_memory=False.
      interactivity=interactivity, compiler=compiler, result=result)

In [16]:

    meta.sample(10)

Out[16]:

| Unnamed: 0 | Unnamed: 0.1 | akon_id | id | altitude | building | city | color | comment | mountain | other | photographer | publisher | publisher_place | region | water_body | year | inventory_number | signature | revision_date | date | feature_class | feature_code | geoname_id | latitude | longitude | name | country_id | admin_name_1 | admin_code_1 | geo | manifest_link | image_links
--- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | ---
243 | 243 | 243 | AK111_476 | 75139 | NaN | NaN | Rochlitz | False | v. 1907 | Rochlitzer Berg | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | Niederösterreichische Landesbibliothek 1672 | 2014-09-05 11:30:43.299 | vor 1907 | T | HLL | 2846260.0 | 51.02678 | 12.77079 | Rochlitzer Berg | DE | NaN | NaN | 51.02678, 12.77079 | https://iiif.onb.ac.at/presentation/AKON/AK111... | [https://iiif.onb.ac.at/images/AKON/AK111_476/...
34809 | 34809 | 34809 | AK073_578 | 45523 | NaN | Kgl. Residenz | Würzburg | False | 1909 gel | NaN | NaN | NaN | Martin | Nürnberg | NaN | NaN | NaN | NaN | NaN | 2014-08-19 14:22:35.340 | gelaufen 1909 | P | PPLA2 | 2805615.0 | 49.79391 | 9.95121 | Würzburg | DE | Bayern | 02 | 49.79391, 9.95121 | https://iiif.onb.ac.at/presentation/AKON/AK073... | [https://iiif.onb.ac.at/images/AKON/AK073_578/...
18069 | 18069 | 18069 | AK023_145 | 13445 | NaN | NaN | Villach | True | NaN | Mittagskogel | NaN | NaN | NaN | NaN | NaN | NaN | 1912.0 | NaN | NaN | 2014-08-04 07:59:10.156 | 1912 | P | PPLA2 | 2762372.0 | 46.61028 | 13.85583 | Villach | AT | NaN | NaN | 46.61028, 13.85583 | https://iiif.onb.ac.at/presentation/AKON/AK023... | [https://iiif.onb.ac.at/images/AKON/AK023_145/...
4554 | 4554 | 4554 | AK034_086 | 20003 | 693.0 | Chorherrensift Vorau | Vorau | False | NaN | NaN | NaN | NaN | Raza | Vorau | NaN | NaN | 1924.0 | NaN | NaN | 2014-09-16 14:48:11.455 | 1924 | S | MSTY | 2762297.0 | 47.40000 | 15.90000 | Stift Vorau | AT | NaN | NaN | 47.4, 15.9 | https://iiif.onb.ac.at/presentation/AKON/AK034... | [https://iiif.onb.ac.at/images/AKON/AK034_086/...
20907 | 20907 | 20907 | AK032_497 | 19311 | NaN | Schloss Purgstall | NaN | False | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 1918.0 | NaN | NaN | 2014-08-04 07:59:10.257 | 1918 | A | ADM3 | 7873031.0 | 48.05513 | 15.13316 | Purgstall an der Erlauf | AT | NaN | NaN | 48.05513, 15.13316 | https://iiif.onb.ac.at/presentation/AKON/AK032... | [https://iiif.onb.ac.at/images/AKON/AK032_497/...
5136 | 5136 | 5136 | AK111_054 | 74715 | NaN | NaN | Kindberg | False | 1901 gel | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | Niederösterreichische Landesbibliothek 1664 | 2014-09-05 10:17:42.132 | gelaufen 1901 | P | PPLA3 | 2774437.0 | 47.50000 | 15.45000 | Kindberg | AT | NaN | NaN | 47.5, 15.45 | https://iiif.onb.ac.at/presentation/AKON/AK111... | [https://iiif.onb.ac.at/images/AKON/AK111_054/...
3871 | 3871 | 3871 | AK125_381 | 83488 | 601.0 | Hans Hackl's Gasthof zum Jaidhaus | Hinterstoder | False | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 1911.0 | NaN | Nationalbibliothek Karten Abteilung 5862 | 2014-09-12 16:07:31.780 | 1911 | P | PPL | 2776235.0 | 47.69957 | 14.15468 | Hinterstoder | AT | NaN | NaN | 47.69957, 14.15468 | https://iiif.onb.ac.at/presentation/AKON/AK125... | [https://iiif.onb.ac.at/images/AKON/AK125_381/...
1174 | 1174 | 1174 | AK116_235 | 77922 | NaN | Burgruine Gars | Gars a. Kamp | False | 1913 gel | NaN | NaN | NaN | Kiennast | Gars | NaN | NaN | NaN | 79/59 K | NaN | 2014-09-09 12:22:52.928 | gelaufen 1913 | P | PPLA3 | 2778845.0 | 48.58333 | 15.65000 | Gars am Kamp | AT | NaN | NaN | 48.58333, 15.65 | https://iiif.onb.ac.at/presentation/AKON/AK116... | [https://iiif.onb.ac.at/images/AKON/AK116_235/...
1897 | 1897 | 1897 | AK118_376 | 65136 | NaN | NaN | NaN | False | 1925 gel | NaN | NaN | NaN | NaN | NaN | NaN | Grundlsee | NaN | 11/44 Kt. | Geogr. Topogr. Bilder-Samml. 1944, 4144 | 2014-09-10 07:51:30.611 | gelaufen 1925 | H | LK | 2777424.0 | 47.63333 | 13.86667 | Grundlsee | AT | NaN | NaN | 47.63333, 13.86667 | https://iiif.onb.ac.at/presentation/AKON/AK118... | [https://iiif.onb.ac.at/images/AKON/AK118_376/...
33243 | 33243 | 33243 | AK083_217 | 52264 | NaN | NaN | Höllenthal | False | v 1905 | NaN | NaN | NaN | Johannes | Partenkirchen-Garmisch | NaN | NaN | NaN | NaN | NaN | 2014-08-26 12:14:56.005 | vor 1905 | T | CRQ | 2900507.0 | 47.43333 | 11.01667 | Höllental Kar | DE | Bayern | 02 | 47.43333, 11.01667 | https://iiif.onb.ac.at/presentation/AKON/AK083... | [https://iiif.onb.ac.at/images/AKON/AK083_217/...

## Split Into Two Sets¶

We'll split the dataframe into two: One with mountains, one without.

In [17]:

    nomountain = meta[ meta['mountain'].isnull() ]
    mountain = meta[ ~ meta['mountain'].isnull() ]

In [18]:

    len(meta), len(nomountain), len(mountain)

Out[18]:

    (34846, 29271, 5575)

Yeah, that adds up.

## Download¶

Ok, so what's left to do?

- Download all image data into two separate directories for training
- Resize the images for the CNN used

VGG16 and VGG19 expect 224x224 pixel RGB images.

Luckily, IIIF allows us to request images already resized to our demands. That saves on bandwidth, time and code complexity.

According to the standard we can use the `size` parameter to resize the image exactly to the dimensions we need.

The links, before and after, would be:

`https://iiif.onb.ac.at/images/AKON/AK024_176/176/full/full/0/native.jpg`

`https://iiif.onb.ac.at/images/AKON/AK024_176/176/full/224,224/0/native.jpg`

Let's try it:

In [19]:

    r = requests.get('https://iiif.onb.ac.at/images/AKON/AK024_176/176/full/224,224/0/native.jpg')

In [20]:

    from IPython.display import display, Image

In [21]:

    display(Image(r.content))

That looks about right.

Download to file:

In [22]:

    import shutil

    def download_to_file(url, filename):
        with requests.get(url, stream=True) as r:
            with open(filename, 'wb') as fh:
                shutil.copyfileobj(r.raw, fh)

    def sized_link(iiif_url, size='224,224'):
        frags = iiif_url.split('/')
        frags[-3] = size
        return '/'.join(frags)

Test that:

In [23]:

    link = sized_link('https://iiif.onb.ac.at/images/AKON/AK024_176/176/full/full/0/native.jpg')
    download_to_file(link, 'testimg.jpg')

In [24]:

    with open('testimg.jpg', 'rb') as fh:
        display(Image(fh.read()))

Create directories:

In [25]:

    import os

    os.mkdir('./images')
    os.mkdir('./images/mountain')
    os.mkdir('./images/nomountain')

Now let's download!

In [26]:

    # For this demonstration we'll just take 10 images each
    for idx, row in mountain.sample(10).iterrows():
        akon_id = row['akon_id']
        for n, link in enumerate(row['image_links']):
            small_image_link = sized_link(link)
            file_name = f'./images/mountain/{akon_id}_{n}.jpg'
            download_to_file(small_image_link, file_name)
            print('.', end='')
    for idx, row in nomountain.sample(10).iterrows():
        akon_id = row['akon_id']
        for n, link in enumerate(row['image_links']):
            small_image_link = sized_link(link)
            file_name = f'./images/nomountain/{akon_id}_{n}.jpg'
            download_to_file(small_image_link, file_name)
            print('.', end='')

    ....................