ホーム 記事一覧 ブック DH週間トピックス 検索 このサイトについて
English
Omeka-SのMroongaSearchモジュールで日本語全文検索を実現する

Omeka-SのMroongaSearchモジュールで日本語全文検索を実現する

はじめに Omeka-Sは強力なデジタルアーカイブシステムですが、デフォルトでは日本語の全文検索がほとんど機能しません 。本記事では、MroongaSearchモジュールを導入することで、日本語全文検索を実現する方法を解説します。 重要:なぜMroongaSearchモジュールが必要なのか Omeka-Sの標準検索の問題点 Omeka-Sの標準フルテキスト検索(FullTextSearchモジュール)は、InnoDBエンジンを使用しており、以下の致命的な問題があります: 日本語単語検索の例 : データ: 「東京大学で人工知能を研究する」 検索語: 「人工知能」 結果: ❌ ヒットしない InnoDBのフルテキスト検索は英語のようなスペース区切り言語を前提としているため、日本語では: 単語検索が不可能 : 文字列全体が1つの単語として扱われる 部分一致も機能しない : FULLTEXTインデックスが日本語を正しく処理できない 検索結果がゼロ : ユーザーは何も見つけられない MroongaSearchモジュールの解決策 MroongaSearchモジュール は、この問題を2段階で解決します: 1. フォールバック機能(モジュール導入直後から有効) 重要 : MroongaSearchモジュールをインストールするだけで、特別な設定なし で日本語検索が動作するようになります。 データ: 「東京大学で人工知能を研究する」 検索語: 「人工知能」 【MroongaSearchモジュールなし】 → ❌ 結果ゼロ 【MroongaSearchモジュールあり(Mroonga未設定でも)】 → ✅ LIKE '%人工知能%' にフォールバック → ✅ 検索結果が返る! MroongaSearchモジュールのフォールバック機能 : CJK(日本語・中国語・韓国語)の単一語検索を自動検出 LIKE '%term%' 検索に自動的にフォールバック Mroongaが設定されていなくても動作する これがないと、日本語全文検索がそもそもうまくいかない 2. Mroonga+TokenMecabによる高速・高精度検索(推奨) さらに、MariaDBにMroongaプラグインを設定すると: ✅ 形態素解析による精密な単語検索 ✅ 高速な全文検索(LIKEの数百倍高速) ✅ AND/OR検索の厳密な制御 MroongaSearchモジュールとは MroongaSearchは、Omeka-S用の全文検索強化モジュールです。 ...

Azure OpenAI GPT-4 vs Document Intelligence: 日本語縦書きOCRの比較検証

Azure OpenAI GPT-4 vs Document Intelligence: 日本語縦書きOCRの比較検証

概要 Microsoft Azureが提供する2つのOCRサービス(Azure OpenAI GPT-4 VisionとAzure Document Intelligence)を使用して、日本語の縦書き原稿用紙のOCR処理を実施し、その結果を詳細に比較検証しました。 検証対象画像 画像ソース : Canvaテンプレート(400字詰め原稿用紙) URL : https://www.canva.com/ja_jp/templates/EAFbqUoH7P8/ 画像の特徴 : 20×20の400字詰め原稿用紙 縦書きレイアウト 薄いグリッド線(マス目) タイトル欄と本文欄の区別 正解データ(Ground Truth) 原稿のタイトル 佐藤ちあき 原稿用紙に書くテキストが入ります。作文や小論文を作ったり、小説を書いたりなどにご活用ください。 このテキストを使用する場合は、日本語の全角を使うことでマスにあった文字を打つことができます。手書きで使用したい場合は、このテキストを削除し、印刷してご使用ください。 1. Azure OpenAI GPT-4.1 による認識結果 認識されたテキスト 原稿のタイトル 佐藤 ちあき 原稿用紙に書くテキストが入ります。作文や小論文を作ったり、小説を書いたりなどにご活用ください。 このテキストを使用する場合は、日本語の全角を使うことでマスにあった文字を打つことができます。手書きで使用したい場合は、このテキストを削除し、印刷してご使用ください。 評価 GPT-4.1は縦書きの原稿用紙に対して以下の特徴を示しました: ✅ タイトルと著者名の順序を正しく認識 ✅ 本文の開始部分を正確に認識 ✅ 原稿用紙のマス目に関する記述を認識 ✅ 縦書きの読み順(右から左)を完璧に理解 ✅ 文章の連続性を保持 正解データとの差異 「佐藤ちあき」→「佐藤 ちあき」(全角スペースが追加) これは画像上でスペースがあるように見えるための合理的な解釈 その他のテキストは完全に一致 精度評価: 99% 2. Azure Document Intelligence による認識結果 認識された領域の可視化 評価 Document Intelligenceは以下の特徴を示しました: ✅ 文字認識能力 - 個々の文字は正確に認識(「佐藤」「ちあき」「原稿」等) ⚠️ 文章の断片化 - マス目ごとに独立した要素として処理され、連続性が失われる ❌ 縦書き読み順の課題 - 縦書きの右から左への流れを適切に処理できない ⚠️ 後処理が必要 - 座標情報を使った再構成により、ある程度の復元は可能 ✅ 座標情報の詳細取得 - 各文字の正確な位置情報は完璧に取得 精度評価: 文字認識精度は約80%、ただし縦書きレイアウトの理解に課題あり ...

LLMによる原稿用紙OCR性能比較:縦書き日本語の認識精度検証

LLMによる原稿用紙OCR性能比較:縦書き日本語の認識精度検証

はじめに 本記事では、実際の原稿用紙画像を用いて 主要LLMモデルのOCR性能を比較検証しました。多くのOCRベンチマークが印刷文書や横書きテキストを対象とする中、日本独自の縦書き原稿用紙という特殊なフォーマット での認識精度を評価することで、各モデルの日本語文書理解能力をより実践的に検証しています。 本検証の特徴 原稿用紙という日本固有のフォーマットを使用 :マス目に収められた文字、縦書きレイアウト、特有の余白構成など、複雑な要素を含む画像での検証 実用シーンを想定 :作文、小説、論文など、実際の執筆場面で使用される原稿用紙での性能評価 最新モデルの網羅的比較 :GPT-5、GPT-4.1、Gemini 2.5 Pro、Claude Opus 4.1、Claude Sonnet 4という最新モデルを同一条件で比較 検証概要 使用画像 画像ソース : Canvaテンプレート(400字詰め原稿用紙) URL : https://www.canva.com/ja_jp/templates/EAFbqUoH7P8/ 画像の特徴 : 20×20の400字詰め原稿用紙 縦書きレイアウト 薄いグリッド線(マス目) タイトル欄と本文欄の区別 検証条件 使用プロンプト : 「OCRして」(全モデル共通) パラメータ : 各モデルのデフォルト設定 実行時期 : 2025年9月 正解テキスト 原稿のタイトル 佐藤 ちあき 原稿用紙に書くテキストが入ります。作文や小論文を作ったり、小説を書いたりなどにご活用ください。 このテキストを使用する場合は、日本語の全角を使うことでマスにあった文字を打つことができます。手書きで使用したい場合は、このテキストを削除し、印刷してご使用ください。 評価方法 本記事の精度スコアは、文字認識の正確性、レイアウト理解、文章構造の保持などを総合的に評価した主観的なスコア です。実用的な観点から、各モデルの強みと課題を分かりやすく数値化しています。 OCR結果詳細 🥇 Gemini 2.5 Pro - 精度スコア: 98/100 原稿のタイトル 佐藤ちあき 原稿用紙に書くテキストが入ります。作文や小論文を作ったり、小説を書いたりなどにご活用ください。 このテキストを使用する場合は、日本語の全角を使うことでマスにあった文字を打つことができます。手書きで使用したい場合は、このテキストを削除し、印刷してご使用ください。 評価ポイント : ✅ 文字認識: ほぼ完璧 ⚠️ 著者名のスペース欠落 ✅ 段落構成: 適切な2段落構成 ✅ レイアウト保持: 優秀 🥈 GPT-5 - 精度スコア: 97/100 原稿のタイトル 佐藤 ちあき 原稿用紙に書くテキストが入ります。作文や小論文を作ったり、小説を書いたりなどにご活用ください。このテキストを使用する場合は、日本語の全角を使うことでマスにあった文字を打つことができます。手書きで使用したい場合は、このテキストを削除し、印刷してご使用ください。 評価ポイント : ...

PDFの透明テキスト抽出における順序保持の課題と解決策

PDFの透明テキスト抽出における順序保持の課題と解決策

はじめに PDFファイルから透明テキストレイヤーを抽出する際、「テキストの順序が元のPDFと異なってしまう」という問題に直面しました。本記事では、この問題の原因と、JavaScriptとPythonそれぞれでの解決策について解説します。誤っている点もあるかもしれませんが、参考になりましたら幸いです。 PDFの透明テキストとは PDFの透明テキストレイヤーは、PDFファイル内に埋め込まれた検索可能なテキスト情報です。OCR処理されたPDFや、デジタル生成されたPDFには、この透明テキストレイヤーが含まれており、以下のような機能を実現しています: テキスト検索 コピー&ペースト スクリーンリーダーによる読み上げ 機械翻訳 問題:テキストの順序が乱れる理由 PDFの内部構造 PDFファイルは、テキストを「コンテンツストリーム」という形式で保存しています。このストリームには、テキストとその位置情報が含まれていますが、必ずしも読む順序で格納されているわけではありません。 例:PDFコンテンツストリームの概念図 [位置: x=100, y=200, テキスト="見出し"] [位置: x=300, y=400, テキスト="脚注"] [位置: x=100, y=300, テキスト="本文"] 一般的な抽出方法の問題点 多くのPDF処理ライブラリは、以下のような手順でテキストを抽出します: コンテンツストリームからテキストと位置情報を取得 座標でソート (上から下、左から右) ソート結果を出力 この「座標でソート」する処理が、テキスト順序の乱れを引き起こす主な原因です。 具体的な問題例 縦書きと横書きの混在 :日本語文書でよく見られる 複数カラムレイアウト :新聞や雑誌形式 図表の挿入 :本文の流れを分断する要素 ヘッダー・フッター :ページをまたぐ要素 解決策:言語別アプローチ JavaScript (PDF.js) での解決策 PDF.jsは、Mozillaが開発したJavaScriptベースのPDFレンダリングライブラリです。 順序を保持する実装 // PDF.jsを使用した順序保持テキスト抽出 async function extractTextWithOrder(page) { // getTextContent()はコンテンツストリームの順序を維持 const textContent = await page.getTextContent(); // itemsは元の順序を保持した配列 const orderedText = textContent.items.map(item => { return { text: item.str, x: item.transform[4], y: item.transform[5], width: item.width, height: item.height }; }); // 配列の順序をそのまま使用(座標ソートしない) return orderedText; } ポイント getTextContent()メソッドは、PDFの内部構造に忠実な順序でテキストを返す 配列のインデックスが元の順序を表現 座標による再ソートを行わない Python (PyMuPDF) での解決策 PyMuPDF(fitz)は、MuPDFライブラリのPythonバインディングです。 ...

TEI/XMLファイルをGitHubで公開する手順書

TEI/XMLファイルをGitHubで公開する手順書

はじめに この記事では、TEI(Text Encoding Initiative)形式のXMLファイルをGitHubにアップロードし、誰でも参照できるURLを作成する手順を説明します。 TEI/XMLは、歴史文献や文学作品などのテキストを構造的に記述するための国際標準形式です。GitHubを使うことで、あなたの研究データを世界中の研究者と共有できるようになります。 必要なもの パソコン(Windows、Mac、Linuxのいずれか) インターネット接続 TEI/XMLファイル(すでにお持ちのもの) メールアドレス(GitHubアカウント作成用) サンプルファイルについて TEI/XMLファイルをお持ちでない方は、以下の『校異源氏物語』のTEI/XMLファイルを練習用として使用できます: サンプルファイルURL : https://raw.githubusercontent.com/kouigenjimonogatari/kouigenjimonogatari.github.io/master/tei/01.xml このファイルをダウンロードする方法: 上記URLをブラウザで開く 右クリック→「名前を付けて保存」を選択 ファイル名を「koukin_genji_01.xml」などに設定して保存 ステップ1:GitHubアカウントの作成 1-1. GitHubのウェブサイトにアクセス ブラウザ(Chrome、Firefox、Safari等)を開きます アドレスバーに https://github.com と入力してEnterキーを押します 1-2. アカウント作成 右上の「Sign up」ボタンをクリックします 以下の情報を入力します: メールアドレス :普段お使いのメールアドレス パスワード :安全なパスワード(8文字以上、数字と記号を含む) ユーザー名 :他の人と重複しない名前(例:yamada-taro2024) 「Create account」をクリックします メールに届いた認証コードを入力します 💡 ヒント : ユーザー名は後から変更できないので、慎重に選びましょう。研究者として使う場合は、実名に基づいた名前がおすすめです。 ステップ2:新しいリポジトリの作成 リポジトリとは、ファイルを保管する「プロジェクトフォルダ」のようなものです。 2-1. リポジトリ作成画面へ GitHubにログインした状態で、右上の「+」マークをクリックします 「New repository」を選択します 2-2. リポジトリの設定 以下の項目を入力・選択します: Repository name(リポジトリ名) 英数字とハイフン(-)が使えます 例:tei-xml-collection、medieval-texts-tei Description(説明)※任意 プロジェクトの簡単な説明 例:「中世文献のTEI/XMLファイル集」 Public/Private(公開設定) Public を選択:誰でも閲覧可能(推奨) Private:招待した人のみ閲覧可能 Initialize this repository with:(初期化オプション) ✅ Add a README file にチェックを入れる その他はそのままで大丈夫です 緑色の「Create repository」ボタンをクリックします ステップ3:TEI/XMLファイルのアップロード 3-1. アップロード画面へ リポジトリが作成されると、ファイル一覧画面が表示されます 「Add file」ボタンをクリックします 「Upload files」を選択します 3-2. ファイルのアップロード 方法A:ドラッグ&ドロップ エクスプローラー(Windows)またはFinder(Mac)でTEI/XMLファイルがある場所を開きます アップロードしたいファイルを選択します(複数選択可) ブラウザの点線枠内にドラッグ&ドロップします 方法B:ファイル選択 「choose your files」をクリックします ファイル選択ダイアログでTEI/XMLファイルを選択します 「開く」をクリックします 実例:『校異源氏物語』のTEI/XMLファイルをアップロード サンプルファイルを使った具体例: ...

TEI ODDファイルのカスタマイゼーション:NDL古典籍OCRの事例

TEI ODDファイルのカスタマイゼーション:NDL古典籍OCRの事例

はじめに TEI (Text Encoding Initiative) は、人文学研究におけるテキストのデジタル化と共有のための国際標準です。本記事では、NDL古典籍OCR-Liteアプリケーションの出力形式に合わせてTEI ODDファイルをカスタマイズした過程を紹介します。 ODD (One Document Does it all) は、TEIスキーマをカスタマイズするための仕組みで、必要な要素と属性だけを含む独自のスキーマを定義できます。 背景:NDL古典籍OCR-Liteアプリケーションの開発 NDL古典籍OCR-Liteの出力結果をTEI/XMLで出力するアプリケーションを作成しています。このアプリケーションは、日本の古典籍をOCR処理し、その結果を標準的なTEI形式で出力することを目的としています。 出力されるTEI XMLには以下の情報を含めることにしました: テキスト情報 : OCRで認識した文字列 レイアウト情報 : 各行の座標情報(バウンディングボックス) 画像参照 : IIIF (International Image Interoperability Framework) 対応の画像URL メタデータ : 文書タイトル、処理情報など このアプリケーションで使用するスキーマをODDで記述してみました。以下、そのカスタマイゼーション過程を紹介します。 カスタマイゼーションのアプローチ 1. 初期アプローチ:標準モジュールの利用 最初は、TEIの標準モジュールを利用してODDを作成しました: schemaSpec ident="ndl_koten_ocr" start="TEI" prefix="tei_"> moduleRef key="tei"/> moduleRef key="header" include="teiHeader fileDesc titleStmt publicationStmt sourceDesc"/> moduleRef key="core" include="p title name resp respStmt lb pb graphic"/> moduleRef key="textstructure" include="TEI text body"/> moduleRef key="transcr" include="facsimile surface zone"/> schemaSpec> include属性の重要性 moduleRef要素のinclude属性は、モジュールから特定の要素のみを選択的に含める重要な機能です: ...

TEI GarageのAPIを使用したODDからRNG/HTMLへの変換

TEI GarageのAPIを使用したODDからRNG/HTMLへの変換

はじめに TEI(Text Encoding Initiative)のODD(One Document Does it all)ファイルから、スキーマ(RNG)やドキュメント(HTML)を生成する作業は、TEIプロジェクトにおいて重要な工程です。本記事では、Roma(TEIのODDエディタ)が内部で使用しているTEI Garage APIの仕組みを解析し、スクリプトから直接APIを呼び出してODDを変換する方法を紹介します。 TEI Garageとは TEI Garageは、TEIコミュニティが提供するWebサービスで、様々なフォーマット間の変換を行うことができます。特にODDファイルの処理において、以下の機能を提供しています: ODD → Compiled ODD への変換 Compiled ODD → RELAX NG スキーマへの変換 ODD → HTML ドキュメントへの変換 その他多数のフォーマット変換 Romaの内部動作を解析 Romaのネットワークトラフィックを観察すると、以下のような変換チェーンを使用していることがわかりました: HTMLドキュメント生成の場合 ODD → ODDC (Compiled ODD) → TEI → xHTML 実際のAPIエンドポイント: https://teigarage.tei-c.org/ege-webservice/Conversions/ODD%3Atext%3Axml/ODDC%3Atext%3Axml/TEI%3Atext%3Axml/xhtml%3Aapplication%3Axhtml%2Bxml/conversion RNGスキーマ生成の場合 ODD → ODDC (Compiled ODD) → RELAXNG 実際のAPIエンドポイント: https://teigarage.tei-c.org/ege-webservice/Conversions/ODD%3Atext%3Axml/ODDC%3Atext%3Axml/relaxng%3Aapplication%3Axml-relaxng/conversion 変換パラメータの詳細 Romaは変換時に以下のようなXML形式のプロパティを送信しています: conversions> conversion index="0"> property id="oxgarage.getImages">falseproperty> property id="oxgarage.getOnlineImages">falseproperty> property id="oxgarage.lang">japroperty> property id="oxgarage.textOnly">falseproperty> property id="pl.psnc.dl.ege.tei.profileNames">defaultproperty> conversion> conversion index="1"> property id="oxgarage.getImages">falseproperty> property id="oxgarage.getOnlineImages">falseproperty> property id="oxgarage.lang">japroperty> property id="oxgarage.textOnly">trueproperty> property id="pl.psnc.dl.ege.tei.profileNames">defaultproperty> conversion> conversions> 各プロパティの意味: ...

Azure Container AppsでNDL古典籍OCR Liteを用いたスケーラブルOCR処理システム

Azure Container AppsでNDL古典籍OCR Liteを用いたスケーラブルOCR処理システム

⚠️ 重要な利用上の注意 本記事で紹介するシステムは、外部サーバーに負荷をかける可能性があります。利用時は十分ご注意ください。 サーバー負荷 : 並列リクエストは対象サーバーに負荷を与えます DoS攻撃のリスク : 大量の同時アクセスはDoS攻撃と誤解される可能性があります 推奨アプローチ : 事前に画像をローカルにダウンロードし、OCR処理のみを並列実行することを推奨します 利用規約の確認 : 対象サーバーの利用規約を必ず確認し、必要に応じて事前許可を取得してください 適切なレート制限 : 実運用では慎重な並列数設定(5-10並列程度)を強く推奨します 責任ある利用 : サーバー管理者や他の利用者への配慮を忘れずに 本記事は技術的な実証実験の記録です。読者の皆様には責任を持った利用をお願いします。 はじめに 本記事では、国立国会図書館(NDL)が開発したNDL古典籍OCR Liteを活用し、Azure Container AppsでスケーラブルなOCR処理システムを構築した事例を紹介します。クラウドネイティブなアーキテクチャにより、従量課金とオートスケーリングを実現したシステムの設計と実装について解説します。 システム概要 アーキテクチャ IIIF画像 → Azure Container Apps → NDL古典籍OCR → TEI XML出力 ↓ オートスケーリング (0-30レプリカ) 主要コンポーネント OCRエンジン : NDL古典籍OCR Lite(日本古典籍特化) インフラ : Azure Container Apps(サーバーレスコンテナ) API設計 : REST API(画像URL → OCR結果) 出力形式 : TEI P5準拠XML スケーリング : 需要に応じた自動スケーリング NDL古典籍OCR Liteの特徴 日本古典籍に最適化されたOCR 縦書きレイアウト対応 : 古典籍特有の縦書き文書構造 読み順序最適化 : 右から左、上から下の日本語読み順 古典文字認識 : くずし字や変体仮名への対応 軽量実装 : Docker化によりクラウドデプロイ対応 Azure Container Appsの選択理由 サーバーレスコンテナの利点 # スケーリング設定例 scale: minReplicas: 0 # アイドル時: コスト0 maxReplicas: 30 # 需要時: 自動拡張 cooldownPeriod: 300 # 5分でスケールダウン コスト最適化 従量課金 : 使用した分のみ課金 0レプリカ : アイドル時は完全にコスト0 自動スケーリング : 需要に応じたリソース調整 システム実装 サーバーサイド実装 # Flask + NDL OCR統合 from flask import Flask, request, jsonify from flask_restx import Api, Resource from simple_ocr_service import OCRService app = Flask(__name__) api = Api(app, doc='/docs/') @api.route('/api/image') class ImageOCR(Resource): def get(self): image_url = request.args.get('image_url') # NDL OCRで画像処理 result = ocr_service.process_single_image(image_url) return result 読み順序アルゴリズム def sort_japanese_reading_order(lines): """日本古典籍の読み順序ソート""" return sorted(lines, key=lambda line: ( -line["bbox"][0], # x座標降順(右→左) line["bbox"][1] # y座標昇順(上→下) )) TEI XML出力 xml version="1.0" encoding="UTF-8"?> TEI xmlns="http://www.tei-c.org/ns/1.0"> teiHeader> fileDesc> titleStmt> title>桐壺title> titleStmt> respStmt> resp>Automated Transcriptionresp> name ref="https://github.com/ndl-lab/ndlkotenocr-lite"> NDL古典籍OCR Lite name> respStmt> fileDesc> teiHeader> facsimile> surface xml:id="surface-1"> zone xml:id="zone-1-1" ulx="3391" uly="1141" lrx="3727" lry="2924" cert="0.799"/> surface> facsimile> text> body> div type="transcription"> pb n="1" facs="#surface-1"/> lb n="1.1" corresp="#zone-1-1" cert="high"/> いづれの御時にか div> body> text> TEI> 処理結果事例 小規模テスト処理(桐壺) 対象 : 東京大学所蔵「桐壺」 ページ数 : 32ページ 処理時間 : 約30秒 成功率 : 100% 並列数 : 10並列 コスト : 約$0.05 パフォーマンス特性 処理時間 = 約1秒/ページ(並列処理時) コスト効率 = $1.5〜2.0/1000ページ スケーリング = 数秒で0→20レプリカ システムの技術的特徴 1. コールドスタート対応 async def process_with_retry(image_url, max_retries=3): """コールドスタート時の自動リトライ""" for attempt in range(max_retries + 1): try: if attempt > 0: wait_time = 2 ** (attempt - 1) await asyncio.sleep(wait_time) return await ocr_request(image_url) except (HTTPError, TimeoutError) as e: if attempt == max_retries: raise e 2. 設定の外部化 # 環境変数による設定 OCR_API_URL=https://your-ocr-service.azurecontainerapps.io DEFAULT_MAX_CONCURRENT=10 DEFAULT_CONFIDENCE_THRESHOLD=0.3 DEFAULT_OUTPUT_FORMAT=xml 3. Swagger UI統合 # API仕様の自動生成 api = Api(app, version='1.0', title='NDL古典籍OCR API', description='日本古典籍専用OCR処理API', doc='/docs/' ) デプロイメント Azure Container Appsデプロイ # コンテナアプリ作成 az containerapp create \ --name ocr-service \ --resource-group rg-ocr \ --environment container-env \ --image registry.azurecr.io/ocr-app:latest \ --target-port 80 \ --ingress external \ --min-replicas 0 \ --max-replicas 30 \ --cpu 2.0 \ --memory 4Gi Docker化 FROM python:3.11-slim # NDL OCRモデル配置 COPY model/ /app/model/ COPY config/ /app/config/ # アプリケーション設定 WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . EXPOSE 80 CMD ["gunicorn", "--bind", "0.0.0.0:80", "app:app"] 運用とモニタリング パフォーマンスメトリクス レスポンス時間 : 平均2-3秒/画像 スループット : 10-15画像/秒(20レプリカ時) 成功率 : 99%以上 コスト効率 : アイドル時$0、処理時のみ課金 ログ監視 # Container Appsログ確認 az containerapp logs show \ --name ocr-service \ --resource-group rg-ocr \ --follow 今後の展望 技術的改善点 画像キャッシュ : 重複処理の削減 バッチ処理 : 効率的な大量処理 GPU対応 : OCR処理の高速化 メトリクス強化 : 詳細な性能分析 応用可能性 デジタルアーカイブ : 図書館・博物館での活用 研究支援 : 人文学研究のデジタル化 教育分野 : 古典文献の教材化 文化保存 : 貴重資料のデジタル保存 まとめ NDL古典籍OCR LiteとAzure Container Appsを組み合わせることで、コスト効率とスケーラビリティを両立した古典籍OCRシステムを構築できました。サーバーレスアーキテクチャにより、従量課金と自動スケーリングを実現し、実用的なデジタルヒューマニティーズツールとして活用可能です。 ...

Omeka SにPROV-Oオントロジーを登録する方法

Omeka SにPROV-Oオントロジーを登録する方法

はじめに Omeka Sでデジタルアーカイブを構築する際、メタデータの記述に標準的な語彙を使用することで、データの相互運用性が向上します。今回は、W3Cが策定したPROV-O(PROV Ontology)をOmeka Sに登録する手順を解説します。 PROV-Oは、データやデジタルオブジェクトの来歴(プロヴェナンス)情報を記述するためのオントロジーで、「誰が」「いつ」「どのように」データを作成・変更したかを構造化して記録できます。 前提条件 Omeka S(バージョン3.0以降)がインストール済み 管理者権限でログイン可能 インターネット接続環境(外部URLからのインポートに必要) 登録手順 1. 語彙管理画面へのアクセス Omeka S管理画面にログイン 左側メニューから「語彙の一覧」をクリック 右上の「新しい語彙を追加」ボタンをクリック 2. 基本情報の入力 語彙の基本情報を以下のように入力します: 項目 入力値 ラベル PROV-Oオントロジー コメント W3C PROV-O (PROV Ontology) - データの来歴情報を記述するための標準オントロジー 名前空間URI http://www.w3.org/ns/prov# 名前空間の接頭語 prov 重要 : 名前空間URIの末尾に#(ハッシュ)が必要です。これを忘れるとプロパティが正しく認識されません。 3. ファイルのインポート設定 Import typeの選択 「URL」を選択します(デフォルトで選択されているはずです)。 File URLの入力 以下のURLを入力します: https://www.w3.org/ns/prov-o このURLは内容交渉(Content Negotiation)に対応しており、Omeka Sが自動的に適切な形式(Turtle)で取得します。 ファイルフォーマット 「Turtle (.ttl)」が自動的に選択されます。変更の必要はありません。 4. 登録の実行 入力内容を確認 「インポート」ボタンをクリック 処理が完了するまで待機(数秒〜数十秒) 5. 登録の確認 登録が完了すると、語彙一覧にPROV-Oオントロジーが表示されます。 クリックして詳細を確認すると、以下のようなクラスとプロパティが登録されているはずです: 主要なクラス: prov:Entity - エンティティ(物や概念) prov:Activity - アクティビティ(プロセスや行為) prov:Agent - エージェント(人、組織、ソフトウェア) 主要なプロパティ: ...

画像コレクション管理ツール 技術アーキテクチャ解説

画像コレクション管理ツール 技術アーキテクチャ解説

概要 以下の記事で、IIIFの機能を簡単に試すことを目的とした「画像コレクション管理」ツールについて紹介しました。 https://zenn.dev/nakamura196/articles/7d6bb4cdc414c4 今回は、このツールの裏側で使われている技術について紹介します。 はじめに 画像コレクション管理ツールは、画像コレクションを国際標準規格であるIIIF(International Image Interoperability Framework)形式で管理・公開するためのWebアプリケーションです。本記事では、このツールの技術的な実装について、特にIIIF仕様の実装と地理空間情報の扱いに焦点を当てて解説します。 技術スタック フロントエンド : Next.js 14 (App Router), React, TypeScript バックエンド : Next.js API Routes データストレージ : AWS S3互換オブジェクトストレージ(Cloudflare R2) 認証 : NextAuth.js 地図表示 : Leaflet, MapLibre GL JS IIIF ビューア : Mirador 3, OpenSeadragon IIIF実装の詳細 1. IIIF Presentation API v2/v3の両方をサポート 本ツールは、IIIF Presentation APIのバージョン2とバージョン3の両方に対応しています。これにより、様々なIIIFビューアとの互換性を確保しています。 v2とv3の主な違い // IIIF v2の構造 { "@context": "http://iiif.io/api/presentation/2/context.json", "@id": "https://example.com/manifest", "@type": "sc:Manifest", "label": "タイトル", "sequences": [{ "@type": "sc:Sequence", "canvases": [...] }] } // IIIF v3の構造 { "@context": "http://iiif.io/api/presentation/3/context.json", "id": "https://example.com/manifest", "type": "Manifest", "label": { "ja": ["タイトル"] }, "items": [...] // canvasの配列 } 2. マルチ言語対応 v3では、ラベルや説明文を言語別に管理できます: ...

IIIF Georeference ViewerのMapLibre GL移行と機能改善

IIIF Georeference ViewerのMapLibre GL移行と機能改善

本記事はAIが作成し、人間が追記しました。 概要 IIIF Georeference ViewerにおけるマップコンポーネントをLeafletからMapLibre GLへ移行し、複数の機能改善を実施しました。本記事では、実装した主要な機能とその技術的詳細について説明します。 https://nakamura196.github.io/iiif_geo/ 主要な改善点 1. 画像の自動回転機能 IIIF画像を地図上に正しい向きで表示するため、コントロールポイント(対応点)から自動的に回転角度を計算する機能を実装しました。 機能概要 画像座標と地理座標の対応点から、画像を北が上になるように回転させる角度を自動計算 2点間または3点以上の分布パターンから最適な回転角度を決定 URLパラメータによる回転角度の保存と復元 実装のポイント // utils/calculateImageRotation.ts export function calculateImageRotation(features: Feature[]): RotationCalculationResult | null { // 最も離れた2点を見つける(より正確な角度計算のため) const validFeatures = features.filter((f) => f.properties?.resourceCoords && f.geometry?.coordinates ); // 画像座標系でのベクトルと地理座標系でのベクトルから回転角度を計算 const imgVector = { x: img2.x - img1.x, y: img2.y - img1.y }; const geoVector = { x: geo2.lng - geo1.lng, y: geo2.lat - geo1.lat }; // 北を基準とした角度の差を計算 const rotationDeg = geoAngleFromNorthDeg - imgAngleDeg; return normalizeAngle(rotationDeg); } UI実装 自動回転ボタン(🔧アイコン)をOSDビューアーに配置 rotationパラメータが未指定の場合は自動的に回転角度を計算 手動での角度調整用スライダーも提供 2. LeafletからMapLibre GLへの移行 移行の背景 パフォーマンス向上 : MapLibre GLはWebGLベースのレンダリングにより、大量のマーカー表示時のパフォーマンスが向上 スムーズなアニメーション : 地図の移動やズーム時のアニメーションがより滑らかに ベクタータイルのサポート : ラスタータイルに加えてベクタータイルの表示が可能 実装のポイント import { Map, NavigationControl, Marker, Popup } from "maplibre-gl"; import "maplibre-gl/dist/maplibre-gl.css"; const mapInstance = ref<Map | null>(null); // MapLibre GL初期化 mapInstance.value = new Map({ container: mapContainer.value!, style: mapStyles.value[0].style, center: initialCenter, zoom: zoom_.value, attributionControl: false }); 3. 現在地表示機能 ブラウザのGeolocation APIを使用して、ユーザーの現在地を地図上に表示する機能を実装しました。 ...

Omeka Sテーマの多言語化

Omeka Sテーマの多言語化

はじめに Omeka Sのテーマ開発において、多言語化の実装方法に関する日本語の情報は限られています。本記事では、Omeka Sのカスタムテーマを多言語対応させる具体的な手順と、実装時の注意点について解説します。 目次 Omeka Sの翻訳システムの仕組み テーマの多言語化に必要なファイル ステップバイステップガイド よくある間違いと解決方法 実装例 トラブルシューティング 1. Omeka Sの翻訳システムの仕組み Omeka Sはgettext という標準的な翻訳システムを使用しています。このシステムでは: .poファイル:人間が読み書きできる翻訳ソースファイル .moファイル:コンパイル済みのバイナリファイル(実際に使用される) $translate()関数:PHPテンプレート内で翻訳を適用 2. テーマの多言語化に必要なファイル 必要なディレクトリ構造 your-theme/ ├── config/ │ └── theme.ini # 重要:has_translations = "true"を追加 ├── language/ │ ├── template.pot # 翻訳テンプレート(オプション) │ ├── ja.po # 日本語翻訳ソース │ └── ja.mo # 日本語翻訳バイナリ └── view/ └── (各種テンプレートファイル) 3. ステップバイステップガイド ステップ1:theme.iniの設定 config/theme.iniファイルの[info]セクションに以下の行を追加します: [info] name = "Your Theme Name" version = "1.0.0" author = "Your Name" description = "Theme description" omeka_version_constraint = "^4.1.0" has_translations = "true" # ← この行が重要! ⚠️ 重要: has_translations = "true"がないと、翻訳ファイルが読み込まれません。 ...

Cantaloupeでdelegate scriptを使ってAzure Storage上のファイルパスを動的に変換する方法

Cantaloupeでdelegate scriptを使ってAzure Storage上のファイルパスを動的に変換する方法

はじめに IIIFサーバーのCantaloupeでAzure Storageを使用している際、IIIF URLのidentifierと実際のAzure Storage上のファイルパスが異なる場合があります。本記事では、この問題をdelegate scriptを使って解決する方法を詳しく解説します。 課題 以下のようなファイル構造で画像を管理しているとします: Azure Storage Container: mycontainer ├── images/ │ ├── collection1/ │ │ ├── item001/ │ │ │ └── item001_001.jpg │ │ └── item002/ │ │ └── item002_001.jpg │ └── collection2/ │ └── ... しかし、IIIF URLでは以下のようにアクセスしたい: https://example.com/iiif/3/collection1/item001/item001_001.jpg/info.json この場合、IIIF URLのidentifier (collection1/item001/item001_001.jpg) と実際のAzure Storageのパス (images/collection1/item001/item001_001.jpg) が異なります。 AzureStorageSourceには、S3SourceのようなPATH_PREFIX設定が存在しないため、この問題を解決するためにはdelegate scriptを使用する必要があります。 解決方法 1. Docker Compose設定 services: cantaloupe: image: islandora/cantaloupe:main environment: CANTALOUPE_SOURCE_STATIC: AzureStorageSource CANTALOUPE_AZURESTORAGESOURCE_ACCOUNT_NAME: ${AZURE_STORAGE_ACCOUNT_NAME} CANTALOUPE_AZURESTORAGESOURCE_ACCOUNT_KEY: ${AZURE_STORAGE_ACCOUNT_KEY} CANTALOUPE_AZURESTORAGESOURCE_CONTAINER_NAME: ${AZURE_STORAGE_CONTAINER_NAME} CANTALOUPE_AZURESTORAGESOURCE_LOOKUP_STRATEGY: ScriptLookupStrategy # 重要 CANTALOUPE_DELEGATE_SCRIPT_ENABLED: "true" CANTALOUPE_DELEGATE_SCRIPT_PATHNAME: "/opt/cantaloupe/delegates.rb" volumes: - "./delegates.rb:/opt/cantaloupe/delegates.rb:ro" labels: - "traefik.enable=true" - "traefik.http.routers.cantaloupe.rule=Host(`example.com`)" - "traefik.http.routers.cantaloupe.entrypoints=websecure" - "traefik.http.routers.cantaloupe.tls=true" - "traefik.http.services.cantaloupe.loadbalancer.server.port=8182" restart: always 2. Delegate Script (delegates.rb) 開発・デバッグ版 最初はデバッグ出力を含む版で動作確認を行います: ...

RELAX NGとSchematronを組み合わせたTEI XMLスキーマの実装ガイド

RELAX NGとSchematronを組み合わせたTEI XMLスキーマの実装ガイド

! 人手で検証を行った後、AIが記事を執筆しました。 はじめに TEI(Text Encoding Initiative)XMLを編集する際、要素や属性の構造検証だけでなく、より複雑なビジネスルールの検証が必要になることがあります。本記事では、RELAX NG(RNG)とSchematronを組み合わせて、構造検証と内容検証の両方を実現する方法を、実際のプロジェクトで直面した課題を例に解説します。 解決したい課題 日本の古典文学テキストをTEI XMLで校訂する際、以下のような要求がありました: ID参照の動的検証 : corresp属性で参照するIDが、実際に文書内のwitness要素に存在することを検証したい Oxygen XML Editorでの補完機能 : 編集時にIDの候補を自動表示したい 複数ID参照のサポート : スペース区切りで複数のIDを指定可能にしたい 特定要素のみ参照を許可 : witness要素のIDのみを参照可能とし、person要素のIDが含まれる場合はエラーにしたい なぜRNG + Schematronなのか? RELAX NGの得意分野 要素・属性の構造定義 データ型の指定 基本的な内容モデルの定義 Schematronの得意分野 XPathベースの複雑な検証ルール 文書内の相互参照チェック カスタムエラーメッセージの提供 この2つを組み合わせることで、構造と内容の両面から厳密な検証が可能になります。 実装例 1. 基本的なRNGスキーマ構造 <?xml version="1.0" encoding="UTF-8"?> <grammar xmlns="http://relaxng.org/ns/structure/1.0" xmlns:a="http://relaxng.org/ns/compatibility/annotations/1.0" xmlns:sch="http://purl.oclc.org/dsdl/schematron" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes" ns="http://www.tei-c.org/ns/1.0"> <!-- Schematron名前空間宣言 --> <sch:ns prefix="tei" uri="http://www.tei-c.org/ns/1.0"/> <!-- ここにSchematronルールを埋め込む --> <start> <ref name="TEI"/> </start> <!-- RNGによる構造定義 --> </grammar> 2. ID定義とanyURI型の活用 Oxygen XML Editorで自動補完を実現するために、anyURI型を使用します: ...

Docker環境でDrupal 10にWDBモジュールをセットアップする手順

Docker環境でDrupal 10にWDBモジュールをセットアップする手順

概要 この記事では、Docker環境でDrupal 10を構築し、言語学データベース用のWDBモジュールをインストールする手順を解説します。 前提条件 Docker Desktop がインストールされていること Git がインストールされていること 手順 1. Docker環境の構築 まず、docker-compose.ymlファイルを作成します: services: mariadb: image: mariadb:latest restart: always volumes: - mariadb:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: drupal MYSQL_DATABASE: drupal MYSQL_USER: drupal MYSQL_PASSWORD: drupal drupal: image: drupal:10.2.7-php8.2-apache-bullseye volumes: - ./drupal/files:/opt/drupal/web/sites/default/files - ./drupal/modules:/opt/drupal/web/modules - ./drupal/themes:/opt/drupal/web/themes - ./drupal/private:/opt/drupal/private depends_on: - mariadb ports: - 8080:80 restart: always volumes: mariadb: {} 次に、必要なディレクトリを作成し、コンテナを起動します: ...

生成AIを用いてプロジェクトに特化したrngファイルを作成する

生成AIを用いてプロジェクトに特化したrngファイルを作成する

概要 TEI/XMLファイルを編集する際、検証に使用するrngファイルを変更することで、使用するタグや属性を限定することができます。これにより、作業者が使用するタグに混乱しない、作成されるTEI/XMLのばらつきが軽減する、といった利点が考えられます。 rngファイルを編集する方法として、以下の記事で紹介したように、Romaを使用する方法が一般的です。 この方法はトップダウン的に使用するタグや属性を限定していく方法ですが、今回は、すでに作成済みのTEI/XMLから、生成AIを用いて、ボトムアップ的にrngファイルを作成してみます。 対象データ 校異源氏物語テキストDBで公開されている以下のXMLファイルを対象とします。 https://kouigenjimonogatari.github.io/tei/01.xml このファイルでは、以下のtei_all.rngを使用しています。 http://www.tei-c.org/release/xml/tei/custom/schema/relaxng/tei_all.rng そのため、以下のように、挿入可能な多くのタグがサジェストされることがわかります。 生成AIによるrngファイルの作成 以下のようなプロンプトにより、対象とするXMLファイルのタグの使用状況などに応じて、rngファイルを作成します。 # XMLファイル用カスタムRNGスキーマ作成依頼 ## 目的 - 作業効率向上のため、使用する要素・属性を限定したRNGスキーマを作成 - 作業者がタグ選択に迷わず、一貫した構造で編集できる環境を構築 ## 提供するファイル - ファイル名: 01.xml ## 要望 1. **現在のXMLファイルを分析**し、実際に使用されている要素・属性を特定 2. **最小限かつ必要十分な要素セット**でRNGスキーマを作成 3. **入力補完・バリデーション**が効果的に働くよう設計 4. **コメント付き**で、各要素の用途を明記 ## 制約・方針 - 不要な要素は除外(例:○○、××は使用しない) - 必須属性を明確に定義 ## 期待する成果物 1. カスタムRNGファイル(.rng) 2. 簡潔な使用ガイド 結果、以下のrngファイルが作成されました。 <?xml version="1.0" encoding="UTF-8"?> <!-- カスタムRNGスキーマ: 校異源氏物語TEIファイル用 作成日: 2024年 【目的】 - 作業効率向上のため、実際に使用する要素・属性のみに限定 - 入力補完とバリデーションを効果的に機能させる - 作業者がタグ選択に迷わない明確な構造 --> <grammar xmlns="http://relaxng.org/ns/structure/1.0" xmlns:a="http://relaxng.org/ns/compatibility/annotations/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes" ns="http://www.tei-c.org/ns/1.0"> <start> <ref name="TEI"/> </start> <!-- ==================== ルート要素 ==================== --> <define name="TEI"> <element name="TEI"> <a:documentation>TEI文書のルート要素</a:documentation> <ref name="teiHeader"/> <ref name="facsimile"/> <ref name="text"/> </element> </define> <!-- ==================== ヘッダー部分(編集対象外) ==================== --> <define name="teiHeader"> <element name="teiHeader"> <a:documentation> メタデータ領域(編集作業では変更しない) - fileDesc: 書誌情報 - encodingDesc: エンコーディング情報 </a:documentation> <ref name="fileDesc"/> <ref name="encodingDesc"/> </element> </define> <!-- 書誌情報 --> <define name="fileDesc"> <element name="fileDesc"> <ref name="titleStmt"/> <ref name="publicationStmt"/> <ref name="sourceDesc"/> </element> </define> <!-- タイトル情報 --> <define name="titleStmt"> <element name="titleStmt"> <element name="title"> <text/> </element> <element name="author"> <text/> </element> <oneOrMore> <ref name="respStmt"/> </oneOrMore> </element> </define> <!-- 責任表示 --> <define name="respStmt"> <element name="respStmt"> <element name="resp"> <attribute name="when"> <a:documentation>作業日(YYYY-MM-DD形式)</a:documentation> <data type="date"/> </attribute> <text/> </element> <element name="name"> <text/> </element> </element> </define> <!-- 出版情報 --> <define name="publicationStmt"> <element name="publicationStmt"> <element name="distributor"> <text/> </element> <element name="date"> <attribute name="when"> <data type="date"/> </attribute> <text/> </element> <element name="availability"> <element name="p"> <element name="ref"> <attribute name="target"> <data type="anyURI"/> </attribute> <text/> </element> </element> </element> </element> </define> <!-- 原資料情報 --> <define name="sourceDesc"> <element name="sourceDesc"> <element name="bibl"> <element name="author"> <text/> </element> <element name="title"> <text/> </element> <element name="publisher"> <text/> </element> </element> </element> </define> <!-- エンコーディング情報 --> <define name="encodingDesc"> <element name="encodingDesc"> <element name="p"> <mixed> <optional> <element name="ref"> <attribute name="target"> <data type="anyURI"/> </attribute> <text/> </element> </optional> </mixed> </element> </element> </define> <!-- ==================== 画像参照部分 ==================== --> <define name="facsimile"> <element name="facsimile"> <a:documentation> 画像情報を管理する領域 - IIIF対応の画像URLとゾーン座標を定義 </a:documentation> <ref name="surfaceGrp"/> </element> </define> <!-- 画像グループ --> <define name="surfaceGrp"> <element name="surfaceGrp"> <attribute name="facs"> <a:documentation>IIIFマニフェストURL(必須)</a:documentation> <data type="anyURI"> <param name="pattern">https://.*\.json</param> </data> </attribute> <oneOrMore> <ref name="surface"/> </oneOrMore> </element> </define> <!-- 個別画像情報 --> <define name="surface"> <element name="surface"> <a:documentation>1つの画像面を表す</a:documentation> <ref name="graphic"/> <oneOrMore> <ref name="zone"/> </oneOrMore> </element> </define> <!-- 画像リンク --> <define name="graphic"> <element name="graphic"> <attribute name="n"> <a:documentation>IIIFキャンバスURL</a:documentation> <data type="anyURI"/> </attribute> <attribute name="url"> <a:documentation>画像の直接URL</a:documentation> <data type="anyURI"/> </attribute> <empty/> </element> </define> <!-- 画像上の領域定義 --> <define name="zone"> <element name="zone"> <a:documentation> 画像上の矩形領域を定義 - ページやコラムの範囲を指定 </a:documentation> <attribute name="xml:id"> <a:documentation>ゾーンID(zone_XXXX形式)</a:documentation> <data type="ID"> <param name="pattern">zone_\d{4}</param> </data> </attribute> <attribute name="lrx"> <a:documentation>右下X座標</a:documentation> <data type="nonNegativeInteger"/> </attribute> <attribute name="lry"> <a:documentation>右下Y座標</a:documentation> <data type="nonNegativeInteger"/> </attribute> <attribute name="ulx"> <a:documentation>左上X座標</a:documentation> <data type="nonNegativeInteger"/> </attribute> <attribute name="uly"> <a:documentation>左上Y座標</a:documentation> <data type="nonNegativeInteger"/> </attribute> <empty/> </element> </define> <!-- ==================== テキスト本文(主要編集領域) ==================== --> <define name="text"> <element name="text"> <a:documentation>テキスト本文のコンテナ</a:documentation> <ref name="body"/> </element> </define> <!-- 本文 --> <define name="body"> <element name="body"> <a:documentation>本文領域</a:documentation> <oneOrMore> <ref name="p"/> </oneOrMore> </element> </define> <!-- 段落 --> <define name="p"> <element name="p"> <a:documentation> 段落要素 - 通常は文書全体で1つのp要素を使用 </a:documentation> <ref name="textContent"/> </element> </define> <!-- ==================== テキストコンテンツモデル ==================== --> <define name="textContent"> <a:documentation> 本文内で使用可能な要素群 </a:documentation> <zeroOrMore> <choice> <text/> <ref name="lb"/> <ref name="pb"/> <ref name="seg"/> </choice> </zeroOrMore> </define> <!-- 改行 --> <define name="lb"> <element name="lb"> <a:documentation> 改行マーカー - 原本の改行位置を示す - 空要素として使用 </a:documentation> <empty/> </element> </define> <!-- ページ境界 --> <define name="pb"> <element name="pb"> <a:documentation> ページ境界マーカー - 新しいページの開始を示す - 必ず対応するzone要素を参照 </a:documentation> <attribute name="n"> <a:documentation>ページ番号(数値)</a:documentation> <data type="positiveInteger"/> </attribute> <attribute name="facs"> <a:documentation>対応するzoneへの参照(#zone_XXXX形式)</a:documentation> <data type="string"> <param name="pattern">#zone_\d{4}</param> </data> </attribute> <empty/> </element> </define> <!-- テキストセグメント(最重要要素) --> <define name="seg"> <element name="seg"> <a:documentation> テキストセグメント 【重要】主要な編集対象要素 - 1行または意味的なまとまりごとに分割 - 各セグメントは固有のIDを持つ外部リソースに対応 - corresp属性で外部データベースとリンク </a:documentation> <attribute name="corresp"> <a:documentation> 外部リソースへのリンク(必須) 形式: https://w3id.org/kouigenjimonogatari/api/items/XXXX-XX.json </a:documentation> <data type="anyURI"> <param name="pattern">https://w3id\.org/kouigenjimonogatari/api/items/\d{4}-\d{2}\.json</param> </data> </attribute> <text/> </element> </define> </grammar> XMLへの適用 以下のように、作成したrngファイルを適用します。 ...

Omeka S Docker の紹介:デジタルコレクションのための最新かつセキュアなソリューション

Omeka S Docker の紹介:デジタルコレクションのための最新かつセキュアなソリューション

! 本記事はAIが作成しました。 Omeka S Docker へようこそ!このプロジェクトは、大学、ギャラリー、図書館、アーカイブ、博物館向けの Web パブリケーションシステムである Omeka S の本番環境対応 Docker セットアップを提供します。 📦 GitHub リポジトリ : https://github.com/nakamura196/omeka-s-docker なぜ Omeka S Docker なのか? デジタルコレクションの管理は複雑である必要はありません。そのため、Omeka S のデプロイと管理を簡素化する Docker ベースのソリューションを作成しました。 主な機能 🚀 クイックセットアップ : シングルコマンドで数分以内に Omeka S を稼働 🔒 セキュリティファースト : 非 root コンテナとセキュアなデフォルト設定を含むセキュリティベストプラクティスで構築 📦 モジュール管理 : 人気の Omeka S モジュールの自動インストールとアップデート 🔄 簡単なアップグレード : データの永続性を保ちながらシームレスなバージョンアップグレード 🐳 本番環境対応 : 開発環境と本番環境の両方に最適化 🌐 Traefik 統合 : リバースプロキシと SSL 終端のビルトインサポート はじめに 前提条件 Docker と Docker Compose がインストールされていること コマンドラインの基本的な知識 (オプション)SSL 付き本番環境デプロイ用のドメイン名 セットアップオプションの理解 この Docker セットアップは2つのデプロイモードを提供します: ...

IIIF 3D Viewerを試作しました。

IIIF 3D Viewerを試作しました。

! 本記事はAIが作成しました。 はじめに デジタルヒューマニティーズの分野において、文化財や歴史的資料の3Dデジタル化が急速に進んでいます。しかし、3Dモデルを単に閲覧するだけでなく、学術的な分析や教育に活用するためには、適切なツールが必要です。本記事では、IIIF(International Image Interoperability Framework)規格に準拠した3Dモデルビューア「IIIF 3D Viewer」について紹介します。 IIIF 3D Viewerとは IIIF 3D Viewerは、IIIF Manifestフォーマットに基づいて3Dモデルを表示し、アノテーション機能を提供するウェブアプリケーションです。 主な特徴 標準規格への準拠 IIIF Presentation API 3.0に準拠 既存のIIIFエコシステムとの親和性 インタラクティブな3D表示 GLB/GLTFフォーマットのサポート マウスやタッチ操作による直感的な操作 WebGLを活用した高速レンダリング アノテーション機能 3Dモデル上の任意の点にアノテーションを追加 3DSelectorタイプによる空間座標の記録 学術的な注釈や解説の付与が可能 多言語対応 日本語・英語のインターフェース 国際的な研究プロジェクトでの利用を想定 静的サイト生成 Next.jsの静的エクスポート機能を活用 GitHub PagesやNetlifyなどで簡単にホスティング可能 技術的な実装 アーキテクチャ 本アプリケーションは、以下の技術スタックで構築されています: フロントエンドフレームワーク : Next.js 15(App Router) 3Dレンダリング : React Three Fiber + Three.js 国際化 : next-intl スタイリング : Tailwind CSS 型安全性 : TypeScript IIIF Manifestの構造 3Dモデルを含むIIIF Manifestの例: { "@context": "http://iiif.io/api/presentation/3/context.json", "id": "https://example.com/manifest.json", "type": "Manifest", "label": { "ja": ["石淵家地球儀"] }, "items": [ { "id": "https://example.com/canvas/1", "type": "Canvas", "items": [ { "id": "https://example.com/annotationpage/1", "type": "AnnotationPage", "items": [ { "id": "https://example.com/annotation/1", "type": "Annotation", "motivation": "painting", "body": { "id": "https://example.com/model.glb", "type": "Model", "format": "model/gltf-binary" }, "target": "https://example.com/canvas/1" } ] } ] } ] } アノテーションの実装 3D空間におけるアノテーションは、以下のような構造で表現されます: ...

「前近代日本-アジア関係資料デジタルアーカイブ」のビューアを試す

「前近代日本-アジア関係資料デジタルアーカイブ」のビューアを試す

概要 「前近代日本-アジア関係資料デジタルアーカイブ」が2025年7月25日に公開されました。 https://asia-da.lit.kyushu-u.ac.jp/ また、以下でビューアが公開されています。 https://github.com/localmedialabs/tei_comparative_viewer 本記事では、本ビューアを試した記録を共有します。 結果、以下のように、セルフホストすることができました。 https://tei-comparative-viewer.aws.ldas.jp/ 以下の「海東諸国紀」のXMLファイルを読み込んでいます。 https://asia-da.lit.kyushu-u.ac.jp/viewer/300 ローカルで起動する 以下に丁寧な説明がなされていますので、手順にしたがって起動させることができました。 https://github.com/localmedialabs/tei_comparative_viewer/blob/main/docs/SETUP.md サーバで起動する サーバで起動するにあたり、Dockerを用いて起動しました。 フォークしたリポジトリは以下です。 https://github.com/nakamura196/tei_comparative_viewer/tree/docker-traefik-setup 以下のようなファイルを用意しました。 FROM php:8.2-fpm # Install system dependencies RUN apt-get update && apt-get install -y \ git \ curl \ libpng-dev \ libonig-dev \ libxml2-dev \ zip \ unzip \ nodejs \ npm \ nginx \ supervisor # Clear cache RUN apt-get clean && rm -rf /var/lib/apt/lists/* # Install PHP extensions RUN docker-php-ext-install mbstring exif pcntl bcmath gd # Get latest Composer COPY --from=composer:latest /usr/bin/composer /usr/bin/composer # Set working directory WORKDIR /var/www # Copy existing application directory contents COPY . /var/www # Install dependencies RUN composer install --no-dev --optimize-autoloader # Install and build frontend assets RUN npm install && npm run build # Remove default nginx site RUN rm -f /etc/nginx/sites-enabled/default # Copy nginx config COPY docker/nginx/app.conf /etc/nginx/sites-available/app.conf RUN ln -s /etc/nginx/sites-available/app.conf /etc/nginx/sites-enabled/ # Copy PHP-FPM config COPY docker/php/www.conf /usr/local/etc/php-fpm.d/www.conf # Copy supervisor config COPY docker/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf # Create necessary directories and set permissions RUN mkdir -p /var/log/supervisor \ && chown -R www-data:www-data /var/www \ && chmod -R 755 /var/www/storage \ && chmod -R 755 /var/www/bootstrap/cache # Generate key RUN php artisan key:generate # Optimize Laravel RUN php artisan config:cache && \ php artisan route:cache && \ php artisan view:cache # Expose port 80 EXPOSE 80 # Create PHP-FPM socket directory RUN mkdir -p /var/run/php # Start supervisord CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] services: app: build: context: . dockerfile: Dockerfile.prod.traefik container_name: tei_viewer_app restart: unless-stopped env_file: - .env.external volumes: - ./storage:/var/www/storage - ./public/assets:/var/www/public/assets networks: - traefik-network labels: - "traefik.enable=true" # HTTP router (redirects to HTTPS) - "traefik.http.routers.app-insecure.rule=Host(`xxx.yyy.zzz`)" - "traefik.http.routers.app-insecure.entrypoints=web" - "traefik.http.routers.app-insecure.middlewares=https-redirect" - "traefik.http.middlewares.https-redirect.redirectscheme.scheme=https" # HTTPS router - "traefik.http.routers.app.rule=Host(`xxx.yyy.zzz`)" - "traefik.http.routers.app.entrypoints=websecure" - "traefik.http.routers.app.tls.certresolver=myresolver" - "traefik.http.services.app.loadbalancer.server.port=80" # Security headers - "traefik.http.middlewares.app-headers.headers.frameDeny=true" - "traefik.http.middlewares.app-headers.headers.contentTypeNosniff=true" - "traefik.http.middlewares.app-headers.headers.browserXssFilter=true" - "traefik.http.middlewares.app-headers.headers.referrerPolicy=strict-origin-when-cross-origin" - "traefik.http.middlewares.app-headers.headers.stsSeconds=31536000" - "traefik.http.middlewares.app-headers.headers.stsIncludeSubdomains=true" - "traefik.http.middlewares.app-headers.headers.stsPreload=true" # Apply middlewares - "traefik.http.routers.app.middlewares=app-headers" networks: traefik-network: external: true # Application Environment APP_ENV=production APP_DEBUG=false APP_KEY= APP_URL=https://xxx.yyy.zzz # Domain Configuration (used in docker-compose labels) DOMAIN=xxx.yyy.zzz # Database DB_CONNECTION=sqlite # Session and Cache SESSION_DRIVER=file CACHE_DRIVER=file # Logging LOG_CHANNEL=stack # Mail (if needed) MAIL_MAILER=smtp # Other Laravel configurations as needed #!/bin/bash echo "=== TEI Comparative Viewer Setup with External Traefik ===" # .env.externalファイルが存在しない場合は作成 if [ ! -f .env.external ]; then echo "Creating .env.external file..." cp .env.external.example .env.external # アプリケーションキーを生成 echo "Generating application key..." docker run --rm \ -v $(pwd):/var/www \ -w /var/www \ php:8.2-cli \ php artisan key:generate --env=production --show | sed 's/base64://' > app_key.tmp # 生成したキーを.env.externalに設定 APP_KEY=$(cat app_key.tmp) sed -i.bak "s/APP_KEY=/APP_KEY=base64:$APP_KEY/" .env.external rm app_key.tmp .env.external.bak echo "Application key generated successfully!" fi # 設定の確認 echo "" echo "⚠️ IMPORTANT: Please edit .env.external and configure the following:" echo " 1. DOMAIN=your-domain.com (your actual domain)" echo " 2. APP_URL=https://your-domain.com (with HTTPS)" echo " 3. ASSET_URL=https://your-domain.com (for proper asset loading)" echo "" echo "Note: This setup assumes you have an external Traefik instance running" echo "with the 'traefik-network' already created." echo "" read -p "Press Enter to continue with the current settings..." # .envファイルをロード export $(cat .env.external | grep -v '^#' | xargs) # traefik-networkが存在するか確認 echo "Checking if traefik-network exists..." if ! docker network ls | grep -q traefik-network; then echo "❌ Error: traefik-network not found!" echo "Please ensure your external Traefik is running with traefik-network created." echo "" echo "If you need to create the network manually:" echo " docker network create traefik-network" exit 1 fi echo "✅ traefik-network found!" # 必要なディレクトリを作成 echo "Creating necessary directories..." mkdir -p storage/app/public mkdir -p storage/framework/{cache,sessions,views} mkdir -p storage/logs mkdir -p bootstrap/cache # まず.envファイルをコピー echo "Copying environment file..." cp .env.external .env # Dockerイメージをビルド(キー生成なしのDockerfileを使用) echo "Building Docker images..." if ! docker compose -f docker-compose-external.yml build; then echo "❌ Docker build failed. Trying without cache..." docker compose -f docker-compose-external.yml build --no-cache fi # コンテナを起動 echo "Starting containers..." docker compose -f docker-compose-external.yml up -d # 起動確認 echo "Waiting for services to be ready..." sleep 15 # アプリケーションキーが設定されていない場合は生成 echo "Checking and generating application key if needed..." if docker compose -f docker-compose-external.yml exec app php artisan key:generate --show | grep -q "base64:"; then echo "Generating new application key..." docker compose -f docker-compose-external.yml exec app php artisan key:generate --force fi # 権限を設定 echo "Setting permissions..." docker compose -f docker-compose-external.yml exec app chown -R www-data:www-data /var/www/storage /var/www/bootstrap/cache || echo "Permission setting completed" # 設定キャッシュをクリア echo "Clearing configuration cache..." docker compose -f docker-compose-external.yml exec app php artisan config:clear docker compose -f docker-compose-external.yml exec app php artisan config:cache docker compose -f docker-compose-external.yml exec app php artisan route:clear # アプリケーションの状態確認 echo "Checking application status..." if docker compose -f docker-compose-external.yml ps | grep -q "Up"; then echo "✅ Application container is running!" else echo "❌ Application container may have issues. Check logs:" echo " docker compose -f docker-compose-external.yml logs app" fi echo "" echo "✅ Setup complete!" echo "" echo "Application should be available at your configured domain (HTTPS)" echo "(assuming your external Traefik is properly configured and SSL certificates are set up)" echo "" echo "To stop the application, run:" echo " docker compose -f docker-compose-external.yml down" echo "" echo "To view logs, run:" echo " docker compose -f docker-compose-external.yml logs -f app" echo "" echo "To check Traefik routing (if dashboard is accessible):" echo " Check your Traefik dashboard for the new routes" echo "" echo "📝 Next steps:" echo " 1. Edit .env.external with your actual domain settings" echo " 2. Update docker-compose-external.yml Traefik labels with your domain" echo " 3. Ensure DNS is properly configured for your domain" まとめ 間違っている点もあるかもしれませんが、参考になりましたら幸いです。 ...

Next.js 15対応 多言語・ダークモード対応SSGテンプレート

Next.js 15対応 多言語・ダークモード対応SSGテンプレート

この記事は人間が実装を確認し、AIが記事を作成しました。 概要 このテンプレートは、Next.js 15を使用した静的サイト生成(SSG)に対応し、多言語対応とダークモードを標準装備したWebアプリケーション開発の出発点です。TypeScript、Tailwind CSS、next-intl、next-themesを組み合わせています。 https://nextjs-i18n-themes-ssg-template.vercel.app/ja/ 主な機能 1. 静的サイト生成(SSG) output: 'export'によるフルスタティックエクスポート 高速なページロードとSEO最適化 ホスティングコストの削減 2. 国際化対応(i18n) next-intlによる完全な多言語サポート 日本語・英語対応(簡単に言語追加可能) URLベースの言語切り替え(/ja/about、/en/about) 型安全な翻訳キー 3. ダークモード next-themesによるシステム連動ダークモード ユーザーの好みを自動検出 スムーズなテーマ切り替えアニメーション LocalStorageによる設定の永続化 4. 開発者体験の向上 TypeScriptによる型安全性 Tailwind CSSによる効率的なスタイリング ESLintによるコード品質管理 統一されたコンポーネント構造 技術スタック { "dependencies": { "next": "^15.4.4", "react": "^19.1.0", "next-intl": "^4.3.4", "next-themes": "^0.4.6", "tailwindcss": "^4.1.11", "@tailwindcss/typography": "^0.5.16" } } プロジェクト構造 src/ ├── app/ │ ├── [locale]/ │ │ ├── layout.tsx # ルートレイアウト │ │ ├── page.tsx # ホームページ │ │ ├── about/ # Aboutページ │ │ └── example/ # サンプルページ │ ├── icon.svg # ファビコン │ └── sitemap.ts # サイトマップ生成 ├── components/ │ ├── layout/ # レイアウトコンポーネント │ │ ├── Header.tsx │ │ ├── Footer.tsx │ │ ├── PageLayout.tsx │ │ ├── ToggleTheme.tsx │ │ └── ToggleLanguage.tsx │ └── page/ # ページ固有コンポーネント ├── i18n/ │ └── routing.ts # i18n設定 └── messages/ # 翻訳ファイル ├── en.json └── ja.json 特徴的な実装 1. sitemap.ts の静的エクスポート対応 export const dynamic = 'force-static'; export const revalidate = false; export default function sitemap(): MetadataRoute.Sitemap { // 実装 } 2. 統一されたページレイアウト <PageLayout breadcrumbItems={breadcrumbItems} title={t('title')} description={t('description')} > <YourContent /> </PageLayout> 3. 環境変数による設定 # .env.example NEXT_PUBLIC_SITE_URL=http://localhost:3000 NEXT_PUBLIC_BASE_PATH= 使い方 インストール git clone [repository-url] cd nextjs-i18n-themes-ssg-template npm install 開発 npm run dev ビルド npm run build カスタマイズポイント 言語追加 : src/i18n/routing.tsとmessages/ディレクトリ ページ追加 : src/app/[locale]/配下に新規ディレクトリ テーマカスタマイズ : tailwind.config.jsとグローバルCSS メタデータ : 各ページのgenerateMetadata関数 ベストプラクティス コンポーネント命名 : PascalCaseを使用 翻訳キー : ネストした構造で整理 型安全性 : TypeScriptの型を最大限活用 パフォーマンス : 静的生成を活用したキャッシュ戦略 まとめ 国際化対応とダークモード機能を標準装備し、SEOに最適化された静的サイトを素早く構築できるよう目指しています。開発者の生産性を向上させながら、エンドユーザーに優れた体験を提供していきたいと思います。 ...