本記事は生成AIと共同で執筆しています。事実関係は可能な範囲で公式ドキュメント等と照合していますが、誤りが含まれている可能性があります。重要な判断を行う前にご自身でも一次情報をご確認ください。

TL;DR

  • IIIF Presentation 3 のアーカイブで OCR テキストをアノテーションとして配信したら、Annona など他の viewer では出るのに Mirador 4.0.0 (projectmirador.org/embed) の Annotations タブだけ空、という現象に遭遇した
  • 原因は Mirador 4.0.0 リリース版config.annotations.filteredMotivations のデフォルト値が ['oa:commenting', 'oa:tagging', 'sc:painting', 'commenting', 'tagging'] で、supplementing を許可していないため
  • Mirador master ブランチでは filteredMotivations: [] (フィルタ無し) に変更されているが、本記事執筆時点でリリースされていない。リリース版では引き続き上記フィルタが効く
  • motivation["commenting", "supplementing"] 配列にすれば、commenting で Mirador 4.0.0 のフィルタを通過しつつ、supplementing も維持して spec 準拠 viewer や text-overlay プラグインから transcription として認識される
  • IIIF Presentation 3 仕様 §3.5「Values for motivation」が定義するのは paintingsupplementing のみ。他の値 (commenting / tagging 等) は Web Annotation Data Model 由来。配列形式の許容根拠は IIIF §4.3 (Properties with Multiple Values) と Web Annotation §3.3.5
  • painting を一緒に指定するのは避ける。各 canvas には既に画像の painting annotation があり、同じ canvas に複数の painting annotation が並ぶと viewer の挙動が実装依存になる

何が起きたか

平賀譲デジタルアーカイブ の API 拡張作業で、コマごとの OCR テキストを IIIF Annotation として配信するエンドポイントを足しました。マニフェストの canvas に annotations 参照を入れて、別 URL で AnnotationPage を返す Presentation 3 の標準構成です。

// /api/iiif/{id}/manifest.json (抜粋)
{
  "items": [
    {
      "id": ".../canvas/p1",
      "type": "Canvas",
      "items": [ /* 画像の painting annotation */ ],
      "annotations": [
        { "id": ".../canvas/p1/annotations", "type": "AnnotationPage" }
      ]
    }
  ]
}
// /api/iiif/{id}/canvas/p1/annotations (修正前)
{
  "@context": "http://iiif.io/api/presentation/3/context.json",
  "id": ".../canvas/p1/annotations",
  "type": "AnnotationPage",
  "items": [
    {
      "id": ".../canvas/p1/annotations/anno-1",
      "type": "Annotation",
      "motivation": "supplementing",
      "body": {
        "type": "TextualBody",
        "language": "ja",
        "format": "text/plain",
        "value": "製造費ノ減少ニ就テ"
      },
      "target": ".../canvas/p1#xywh=4875,1610,88,1282"
    }
  ]
}

motivation には IIIF Cookbook の Recipe 0068 (Newspaper)0231 (Transcripts/Captions メタレシピ) が transcription / OCR で推奨する supplementing を採用しています。Annona など spec に忠実な viewer ではこれで正しくキャンバスに重ね描画されます。

ところが Mirador 4.0.0 (https://projectmirador.org/embed/?iiif-content=...) の Annotations タブを開いても項目が表示されない。manifest を読み込めて、画像も検索 (IIIF Content Search) も動いているのに、Annotations だけが空でした。

ちなみに IIIF Cookbook 0068 自身の公式マニフェストを Mirador 4.0.0 embed にロードしても、同じく Annotations タブは空になります(試せます: collection / issue manifest 直)。仕様準拠の標準例ですら出ないので、ライブラリ側の挙動として固定的なものだと当たりが付きます。

原因の特定 — 配信されている Mirador bundle を読む

最初は「Mirador 4 が motivation supplementing を panel に出さないのは仕様上 / コード上どうなっているのか」を master ブランチのソース (src/config/settings.js) で調べたところ、

annotations: {
  htmlSanitizationRuleSet: 'iiif',
  // filteredMotivations: if empty, all annotation motivations will be shown.
  filteredMotivations: [],
},

と、フィルタなし(空配列) がデフォルトに見えました。ところが実観察と矛盾するため、実際に projectmirador.org/embed から配信されている bundle を直接読み解きます。

$ curl -sS https://projectmirador.org/embed/ | grep -oE 'src="[^"]+\.js"'
src="/assets/embed-BxGPuA7M.js"

$ curl -sS https://projectmirador.org/assets/embed-BxGPuA7M.js | grep -oE 'mirador[^"]{0,40}\.js'
mirador.es-Bh8H5vm3.js

$ curl -sS https://projectmirador.org/assets/mirador.es-Bh8H5vm3.js \
   | grep -oE 'filteredMotivations:\[[^]]+\]'
filteredMotivations:["oa:commenting","oa:tagging","sc:painting","commenting","tagging"]

実際にデプロイされている bundle には、明示的に filteredMotivations が設定されています。supplementing は含まれません。

リリースタグごとの定義を遡ると:

mirador@v4.0.0  src/config/settings.js
  filteredMotivations: ['oa:commenting','oa:tagging','sc:painting','commenting','tagging']

mirador@master (未リリース)  src/config/settings.js
  filteredMotivations: []

つまり、Mirador 4.0.0 リリースには supplementing を panel に出さない デフォルトが既定として入っていて、master では「全て出す」に変更されているけれど、まだリリースされていない、というのが現状です。projectmirador.org/embed もリリースバンドルを使っているので、フィルタが効きます。

セレクタ実装は src/state/selectors/annotations.js 由来 (bundle 内のミニファイ後コードでも該当ロジックが確認できる):

const getMotivations = createSelector(
  [getConfig, (state, { motivations }) => motivations],
  (config, motivations) => motivations || config.annotations.filteredMotivations,
);

export const getAnnotationResourcesByMotivationForCanvas = createSelector(
  [getPresentAnnotationsCanvas, getMotivations],
  (annotations, motivations) => {
    const resources = flatten(annotations.map(a => a.resources));
    if (!motivations || motivations.length === 0) return resources;
    return resources.filter(r => r.motivations.some(m => motivations.includes(m)));
  },
);

Mirador 4.0.0 では filteredMotivations が空配列でないため、r.motivations.some(m => motivations.includes(m)) の判定が効き、supplementing だけのアノテーションは振り落とされます。

解 — motivation を配列にする

motivation は IIIF/Web Annotation 仕様で複数値を取れるので、両方つけます。Mirador 4.0.0 の filter を commenting で通過させ、spec 準拠 viewer や text-overlay プラグインのために supplementing も残す。

{
  "motivation": ["commenting", "supplementing"],
  "body": {
    "type": "TextualBody",
    "language": "ja",
    "format": "text/plain",
    "value": "製造費ノ減少ニ就テ"
  },
  "target": ".../canvas/p1#xywh=4875,1610,88,1282"
}

実装側の変更は文字列リテラルを配列リテラルに直すだけです。TypeScript の場合は型注釈を 'supplementing' から tuple 型 ['commenting', 'supplementing'] に変えるのを忘れないように。

これで Mirador 4.0.0 panel に項目が出るようになります。

仕様上の根拠 — どこに書いてあるか

motivation の値域と複数値許容について、IIIF Presentation 3 仕様自体は限定的にしか書いていないので、Web Annotation Data Model を参照する形になります。整理すると:

  • IIIF Presentation 3 §3.5「Values for motivation が定義するのは paintingsupplementing の 2 値のみ。同セクションの第 3 段落で「他の motivation 値は Web Annotation Data Model に従って使用してよい」(SHOULD be used) と委ねている。
  • IIIF §4.3「Properties with Multiple Values: 複数値を取るプロパティは値が 1 つでも配列にする必要がある。
  • Web Annotation Data Model §3.3.5 「Motivation and Purpose: 「There SHOULD be exactly 1 motivation for each Annotation, and MAY be 0 or more than 1.」つまり 0 個・1 個・複数個いずれも許容。

なお commenting / tagging / bookmarking 等の motivation 値は IIIF spec ではなく Web Annotation Data Model §3.3.5 で定義されている語彙です。Mirador 4.0.0 のデフォルト filter リスト ['oa:commenting','oa:tagging','sc:painting','commenting','tagging'] のうち oa: / sc: 接頭辞ありは古い (open annotation / shared canvas) 名前空間で、prefix なしの commenting / tagging が Web Annotation 1.0 系の現行表記です。

painting を一緒に指定したくならないか

「同じ場所にテキストを描いてほしいだけなら painting motivation で TextualBody を渡せば良いのでは?」と最初は思いますが、これは避けた方が良い設計です。

IIIF Presentation 3 の各 canvas は既に画像を painting annotation として持っています:

{
  "id": ".../canvas/p1",
  "type": "Canvas",
  "items": [
    {
      "type": "AnnotationPage",
      "items": [
        {
          "type": "Annotation",
          "motivation": "painting",
          "body": { "type": "Image", "id": ".../full/full/0/default.jpg", ... },
          "target": ".../canvas/p1"
        }
      ]
    }
  ]
}

ここに OCR テキストの painting を追加すると、同じ canvas を target にする painting annotation が複数並ぶ状態になります。仕様上は許容されますが、viewer がどう描画するかは実装依存で、

  • 後勝ちで画像が消えてテキストだけになる
  • テキスト側を空ボックスとして描画して画像を覆う
  • どちらも描画するが重なって読めない

など、安定しません。OCR テキストは画像を置き換えるものではなく補足するものなので、supplementing 系の motivation で扱うのが意図に合っています。Mirador 4.0.0 panel 対応のために便宜上 commenting を併記する、というのが今回の落とし所でした。

まとめ — Mirador (4.0.0) 互換チェックリスト

OCR テキストを IIIF Annotation として Mirador 4.0.0 で表示させたい場合:

  1. motivation["commenting", "supplementing"] の配列で出す
    • Mirador 4.0.0 のリリース版では filteredMotivations のデフォルトが supplementing を含まないため、単独だと panel に出ない
    • master では既に [] に変更されているので、新版がリリースされたら片方でも構わない(が、後方互換のため両方入れておくのが堅い)
  2. painting は足さない(画像の painting annotation と衝突する)
  3. annotation の target は manifest 内の canvas の id と一致 + #xywh= で領域指定
  4. coords の単位は manifest 内 canvas の width/height と同じピクセル空間
    • OCR 元画像が IIIF source より小さい場合、xywh をスケールアップする必要がある 仕様 (supplementing 推奨) と実装 (Mirador 4.0.0 のフィルタ) のずれによる罠なので、最初から配列形式で出しておくのが無難です。Mirador 4 の今後のリリースでフィルタデフォルトが緩くなれば、この workaround は不要になる見込みです。

参考