はじめに

Webサイト上で高解像度画像をスムーズにズーム表示するために使用されるDeep Zoom技術。Microsoft Deep Zoom Composerなどで生成されたタイル化された画像データから、元の高解像度画像を復元する必要に迫られることがあります。

本記事では、Deep Zoom形式で公開されている画像データから、元の高解像度TIFF画像を復元する技術について解説します。

Deep Zoom画像の仕組み

タイル構造

Deep Zoom画像は、1枚の大きな画像を複数の小さなタイル画像に分割し、ピラミッド構造で保存します:

  • レベル0 : 最も低解像度(通常1タイル)
  • レベルN : 最高解像度(元画像の解像度に相当)
  • 各レベルで解像度が2倍になる

ファイル構成

dzc_output.xml              # メタデータ
dzc_output_files/
  ├── 0/
  │   └── 0_0.jpg          # レベル0の唯一のタイル
  ├── 1/
  │   ├── 0_0.jpg
  │   └── ...
  └── 16/                   # 最高レベル
      ├── 0_0.jpg
      ├── 0_1.jpg
      └── ...               # 数万枚のタイル

実装の課題と解決策

課題1: XMLメタデータの名前空間の違い

Deep Zoomには複数のバージョンがあり、XMLの名前空間が異なります:

  • http://schemas.microsoft.com/deepzoom/2008
  • http://schemas.microsoft.com/deepzoom/2009

解決策 : 複数の名前空間に対応した柔軟なXMLパーサーを実装

def fetch_xml_info(xml_url):
    root = ET.fromstring(response.content)

    # 複数の名前空間を試行
    namespaces = [
        '{http://schemas.microsoft.com/deepzoom/2008}Size',
        '{http://schemas.microsoft.com/deepzoom/2009}Size',
        'Size'
    ]

    for ns in namespaces:
        image_elem = root.find('.//' + ns)
        if image_elem is not None:
            break

    width = int(image_elem.attrib['Width'])
    height = int(image_elem.attrib['Height'])
    return config

課題2: 最大レベルの自動検出

XMLに記載された最大レベルと、実際にサーバー上に存在するレベルが異なる場合があります。

解決策 : 実際にHEADリクエストを送信して存在確認

def find_actual_max_level(base_url, format_ext):
    """実際に存在する最大レベルを検出"""
    for level in range(20, -1, -1):
        url = f"{base_url}{level}/0_0.{format_ext}"
        try:
            response = requests.head(url, timeout=10)
            if response.status_code == 200:
                return level
        except:
            continue
    return None

課題3: 大量タイルの効率的ダウンロード

高解像度画像では、数万枚のタイルをダウンロードする必要があります(例: 29,146タイル)。

解決策 : ThreadPoolExecutorによる並列ダウンロード

def download_tiles(tiles_list, base_url, level, format_ext):
    session = requests.Session()
    downloaded_tiles = []

    with ThreadPoolExecutor(max_workers=10) as executor:
        futures = {
            executor.submit(download_tile, base_url, level,
                          col, row, format_ext, session): (col, row)
            for col, row in tiles_list
        }

        with tqdm(total=len(futures)) as pbar:
            for future in as_completed(futures):
                result = future.result()
                if result:
                    downloaded_tiles.append(result)
                pbar.update(1)

    return downloaded_tiles

課題4: タイルオーバーラップの処理

Deep Zoomのタイルには、シームレスな表示のためのオーバーラップ(重複領域)があります。

解決策 : オーバーラップを考慮した座標計算

def reconstruct_image(tiles, tile_size, overlap):
    for col, row, tile_img in tiles:
        # オーバーラップを考慮した配置座標
        x = col * (tile_size - overlap)
        y = row * (tile_size - overlap)
        canvas.paste(tile_img, (x, y))

課題5: 大容量画像の保存

復元した画像は数GB規模になることがあり、標準TIFFの4GB制限を超える場合があります。

解決策 : BigTIFF形式での保存

def save_bigtiff(image, output_path):
    # まずPNGで保存(ファイルサイズ制限なし)
    png_file = output_path.replace('.tif', '.png')
    image.save(png_file, format='PNG', compress_level=6)

    # tifffileライブラリでBigTIFFに変換
    import tifffile
    img_array = np.array(image)
    tifffile.imwrite(
        output_path,
        img_array,
        bigtiff=True,           # BigTIFF有効化
        compression='deflate',   # 圧縮
        tile=(256, 256)         # タイル化
    )

実装結果

処理性能

画像サイズタイル数ダウンロード時間最終ファイルサイズ
62533 x 2973428,899約12分3.7GB (TIFF)
62588 x 2980029,146約12分3.4GB (TIFF)
7760 x 103281,271約2分72MB (TIFF)

並列処理の効果

  • 並列度: 10スレッド
  • 平均ダウンロード速度: 約40タイル/秒
  • ネットワーク帯域幅の効率的な利用

技術スタック

# 主要ライブラリ
import requests          # HTTP通信
from PIL import Image    # 画像処理
import numpy as np       # 配列操作
import tifffile          # BigTIFF対応
from concurrent.futures import ThreadPoolExecutor
from tqdm import tqdm    # プログレスバー

コード構成

download_deepzoom.py           # 単一画像処理
batch_download_deepzoom.py     # バッチ処理
├─ fetch_xml_info()           # XML解析
├─ find_actual_max_level()    # レベル自動検出
├─ download_tile()            # タイルダウンロード
├─ reconstruct_image()        # 画像復元
└─ save_bigtiff()             # BigTIFF保存

最適化のポイント

1. セッションの再利用

session = requests.Session()
# 同一セッションでHTTP接続を再利用
response = session.get(url)

2. エラーハンドリング

try:
    response = session.get(url, timeout=30)
    if response.status_code == 200:
        return Image.open(BytesIO(response.content))
except Exception as e:
    print(f"Failed to download tile: {e}")
    return None

3. メモリ効率

  • タイルごとに処理し、メモリ使用量を抑制
  • 大きなキャンバスを一度だけ作成

応用例

デジタルアーカイブ

  • 古文書・美術品の高解像度画像保存
  • 地図データの完全復元
  • 文化財のデジタル保存

データ移行

  • プラットフォーム移行時の画像データ変換
  • バックアップ目的での完全画像取得
  • オフライン環境での画像利用

まとめ

Deep Zoom画像の復元には以下の技術的課題がありましたが、適切な対処により完全な復元が可能になりました:

  1. ✓ XMLの名前空間の違いへの対応
  2. ✓ 実際の最大レベルの自動検出
  3. ✓ 大量タイルの並列ダウンロード
  4. ✓ オーバーラップを考慮した画像復元
  5. ✓ BigTIFF形式での大容量画像保存

本手法により、6万×3万ピクセル級の超高解像度画像を、約12分で完全復元することができました。

参考資料

  • Microsoft Deep Zoom Specification
  • PIL/Pillow Documentation
  • tifffile Library Documentation
  • Python concurrent.futures

注意事項 : 本記事の技術は、適切な権限を持つ画像データに対してのみ使用してください。著作権やライセンスを遵守した利用を心がけましょう。