🚀 OpticMCP
OpticMCPは、AIアシスタント向けのカメラ/ビジョンツールを提供するモデルコンテキストプロトコル(MCP)サーバーです。カメラに接続し、画像をキャプチャして大規模言語モデル(LLM)で使用できます。
🚀 クイックスタート
OpticMCPを使用することで、AIアシスタントにカメラやビジョンツールを提供できます。以下のセクションでは、OpticMCPのインストール、使用方法、設定方法などについて説明します。
✨ 主な機能
対応カメラタイプ
OpticMCPは、あらゆるタイプのカメラをサポートすることを目指しています。
- USBカメラ ✅
- IP/ネットワークカメラ ✅ - RTSP、HLS、MJPEGストリーム
- 画面キャプチャ ✅ - デスクトップ/モニターのキャプチャ
- HTTP画像 ✅ - URLから画像をダウンロード
- QR/バーコードデコード ✅ - QRコードやバーコードをデコード
- 画像分析 ✅ - メタデータ、統計情報、ヒストグラム、代表色の抽出
- 画像比較 ✅ - SSIM、MSE、知覚ハッシュ、ビジュアル差分
- 検出 ✅ - 顔検出、動き検出、エッジ検出
- Raspberry Piカメラ (計画中) - CSIカメラモジュール
- モバイルカメラ (計画中) - 携帯電話のカメラとの統合
現在の機能
USBカメラ
- list_cameras - 利用可能なすべてのUSBカメラをスキャンしてリスト表示します。
- save_image - フレームをキャプチャし、直接ファイルに保存します。
カメラストリーミング
- start_stream - カメラをローカルホストのHTTPサーバー(MJPEG)にストリーミングを開始します。
- stop_stream - カメラのストリーミングを停止します。
- list_streams - すべてのアクティブなカメラストリームをリスト表示します。
マルチカメラダッシュボード
- start_dashboard - すべてのアクティブなカメラストリームをレスポンシブなグリッドで表示する動的なダッシュボードを起動します。
- stop_dashboard - ダッシュボードサーバーを停止します。
RTSPストリーム
- rtsp_save_image - RTSPストリームからフレームをキャプチャして保存します。
- rtsp_check_stream - RTSPストリームを検証し、プロパティを取得します。
HLSストリーム (HTTP Live Streaming)
- hls_save_image - HLSストリームからフレームをキャプチャして保存します。
- hls_check_stream - HLSストリームを検証し、プロパティを取得します。
MJPEGストリーム
- mjpeg_save_image - MJPEGストリーム(IPカメラ、ESP32 - CAMなどで一般的)からフレームをキャプチャします。
- mjpeg_check_stream - MJPEGストリームの可用性を検証します。
画面キャプチャ
- screen_list_monitors - 利用可能なすべてのモニター/ディスプレイをリスト表示します。
- screen_save_image - モニターの全画面スクリーンショットをキャプチャします。
- screen_save_region - 画面の特定の領域をキャプチャします。
HTTP画像
- http_save_image - 任意のURLから画像をダウンロードして保存します。
- http_check_image - URLが有効な画像を指しているかを確認します。
QR/バーコードデコード (libzbarが必要)
- decode_qr - 画像からQRコードをデコードします。
- decode_barcode - 画像からバーコード(EAN、UPC、Code128など)をデコードします。
- decode_all - 画像からすべてのQRコードとバーコードをデコードします。
- decode_and_annotate - コードをデコードし、バウンディングボックス付きの注釈付き画像を保存します。
画像分析
- image_get_metadata - 画像ファイルからEXIFデータを含むメタデータを抽出します。
- image_get_stats - 明るさ、コントラスト、シャープネスを計算します。
- image_get_histogram - オプションで可視化したカラーヒストグラムを生成します。
- image_get_dominant_colors - K-meansクラスタリングを使用して代表色を抽出します。
画像比較
- image_compare_ssim - 構造的類似度指数を使用して画像を比較します。
- image_compare_mse - 平均二乗誤差を使用して画像を比較します。
- image_compare_hash - 知覚ハッシュ(phash、dhash、ahash)を使用して画像を比較します。
- image_get_hash - 画像の知覚ハッシュを生成します。
- image_diff - 差異を強調したビジュアル差分を作成します。
- image_compare_histograms - カラーヒストグラムによって画像を比較します。
検出
- detect_faces - HaarカスケードまたはDNNを使用して画像から顔を検出します。
- detect_faces_save - 顔を検出し、バウンディングボックス付きの注釈付き画像を保存します。
- detect_motion - 2つのフレーム間の動きを検出します。
- detect_edges - Canny、Sobel、またはLaplacianを使用して画像のエッジを検出します。
- detect_objects - MobileNet SSDを使用して一般的な物体を検出します。
📦 インストール
PyPIからのインストール (推奨)
pip install optic-mcp
またはuvを使用する場合:
uv pip install optic-mcp
ソースからのインストール
git clone https://github.com/Timorleiderman/OpticMCP.git
cd OpticMCP
uv sync
💻 使用例
MCPサーバーの実行
PyPIからインストールした場合:
optic-mcp
またはuvxを使用する場合(インストール不要):
uvx optic-mcp
ソースからの実行
uv run optic-mcp
MCPの設定
Claude Desktop
Claude Desktopの設定ファイルに追加します。
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"optic-mcp": {
"command": "uvx",
"args": ["optic-mcp"]
}
}
}
OpenCode
opencode.json(プロジェクトディレクトリの.opencode/またはグローバルの~/.opencode/)に追加します。
{
"mcp": {
"optic-mcp": {
"type": "local",
"command": ["uvx", "optic-mcp"]
}
}
}
その他のMCPクライアント
uvxを使用する場合(推奨 - インストール不要):
{
"mcpServers": {
"optic-mcp": {
"command": "uvx",
"args": ["optic-mcp"]
}
}
}
pipでインストールした場合:
{
"mcpServers": {
"optic-mcp": {
"command": "optic-mcp"
}
}
}
ソースからの場合:
{
"mcpServers": {
"optic-mcp": {
"command": "uv",
"args": ["run", "--directory", "/path/to/OpticMCP", "optic-mcp"]
}
}
}
ツールの使用
list_cameras
利用可能なUSBカメラ(インデックス0 - 9)をスキャンし、その状態を返します。
[
{
"index": 0,
"status": "available",
"backend": "AVFOUNDATION",
"description": "Camera 0 (AVFOUNDATION)"
}
]
save_image
フレームをキャプチャし、ディスクに保存します。
パラメータ:
file_path (str) - 画像を保存するパス
camera_index (int, デフォルト: 0) - キャプチャするカメラのインデックス
戻り値: ファイルパス付きの成功メッセージ
ストリーミングツール
カメラをローカルのHTTPサーバーにストリーミングし、任意のブラウザでリアルタイムで表示できます。
start_stream
カメラをローカルホストのHTTPサーバーにストリーミングを開始します。ストリームは広くサポートされているMJPEG形式を使用します。
パラメータ:
camera_index (int, デフォルト: 0) - ストリーミングするカメラのインデックス
port (int, デフォルト: 8080) - ストリームを提供するポート
戻り値: ストリームのURLと状態を含む辞書
{
"status": "started",
"camera_index": 0,
"port": 8080,
"url": "http://localhost:8080",
"stream_url": "http://localhost:8080/stream"
}
使用方法:
- ブラウザで
http://localhost:8080を開き、シンプルなUIでストリームを表示します。
http://localhost:8080/streamを使用して、生のMJPEGストリームを取得し(他のアプリケーションに埋め込むことができます)。
stop_stream
カメラのストリーミングを停止します。
パラメータ:
camera_index (int, デフォルト: 0) - ストリーミングを停止するカメラのインデックス
戻り値: 状態を含む辞書
list_streams
すべてのアクティブなカメラストリームをリスト表示します。
戻り値: URLとポートを含むアクティブなストリーム情報のリスト
ダッシュボードツール
start_dashboard
動的なマルチカメラダッシュボードサーバーを起動します。ダッシュボードは自動的にすべてのアクティブなカメラストリームを検出し、レスポンシブなグリッドレイアウトで表示します。
パラメータ:
port (int, デフォルト: 9000) - ダッシュボードを提供するポート
戻り値: ダッシュボードのURLと状態を含む辞書
{
"status": "started",
"port": 9000,
"url": "http://localhost:9000"
}
使用方法:
start_streamで1つ以上のカメラストリームを開始します。
start_dashboardでダッシュボードを起動します。
- ブラウザで
http://localhost:9000を開きます。
- ダッシュボードは3秒ごとに自動更新され、新しい/削除されたストリームを検出します。
stop_dashboard
ダッシュボードサーバーを停止します。
戻り値: 状態を含む辞書
RTSPツール
⚠️ 重要提示
RTSP機能は、実際のRTSPハードウェア/ストリームでテストされていません。実装はされていますが、特定のカメラベンダーに合わせて調整が必要な場合があります。
rtsp_save_image
RTSPストリームからフレームをキャプチャし、ディスクに保存します。
パラメータ:
rtsp_url (str) - RTSPストリームのURL(例: rtsp://ip:554/stream)
file_path (str) - 画像を保存するパス
timeout_seconds (int, デフォルト: 10) - 接続タイムアウト
戻り値: ファイルパス付きの成功メッセージ
rtsp_check_stream
RTSPストリームを検証し、ストリーム情報を返します。
パラメータ:
rtsp_url (str) - 検証するRTSPストリームのURL
timeout_seconds (int, デフォルト: 10) - 接続タイムアウト
戻り値: ストリームの状態とプロパティ(幅、高さ、fps、コーデック)を含む辞書
HLSツール
hls_save_image
HLSストリームからフレームをキャプチャし、ディスクに保存します。
パラメータ:
hls_url (str) - HLSストリームのURL(通常は.m3u8で終わります)
file_path (str) - 画像を保存するパス
timeout_seconds (int, デフォルト: 30) - 接続タイムアウト
戻り値: ファイルパス付きの成功メッセージ
hls_check_stream
HLSストリームを検証し、ストリーム情報を返します。
パラメータ:
hls_url (str) - 検証するHLSストリームのURL
timeout_seconds (int, デフォルト: 30) - 接続タイムアウト
戻り値: ストリームの状態とプロパティ(幅、高さ、fps、コーデック)を含む辞書
MJPEGツール
mjpeg_save_image
MJPEGストリーム(IPカメラ、ESP32 - CAM、Arduinoカメラで一般的)からフレームをキャプチャします。
パラメータ:
mjpeg_url (str) - MJPEGストリームのURL(例: http://camera/video.mjpg)
file_path (str) - 画像を保存するパス
timeout_seconds (int, デフォルト: 10) - 接続タイムアウト
戻り値: 状態、ファイルパス、サイズ(バイト)を含む辞書
mjpeg_check_stream
MJPEGストリームのURLを検証します。
パラメータ:
mjpeg_url (str) - 検証するMJPEGストリームのURL
timeout_seconds (int, デフォルト: 10) - 接続タイムアウト
戻り値: 状態、URL、コンテンツタイプを含む辞書
画面キャプチャツール
screen_list_monitors
利用可能なすべてのモニター/ディスプレイをリスト表示します。
戻り値: ID、寸法、位置を含むモニターのリスト
screen_save_image
モニターの全画面スクリーンショットをキャプチャします。
パラメータ:
file_path (str) - 画像を保存するパス
monitor (int, デフォルト: 0) - モニターのインデックス(0 = すべてのモニターを結合)
戻り値: 状態、ファイルパス、寸法を含む辞書
screen_save_region
画面の特定の領域をキャプチャします。
パラメータ:
file_path (str) - 画像を保存するパス
x (int) - 左上隅のX座標
y (int) - 左上隅のY座標
width (int) - 幅(ピクセル)
height (int) - 高さ(ピクセル)
戻り値: 状態、ファイルパス、領域の詳細を含む辞書
HTTP画像ツール
http_save_image
URLから画像をダウンロードし、ディスクに保存します。
パラメータ:
url (str) - 画像のURL(http://またはhttps://)
file_path (str) - 画像を保存するパス
timeout_seconds (int, デフォルト: 30) - 接続タイムアウト
戻り値: 状態、ファイルパス、サイズ(バイト)、コンテンツタイプを含む辞書
http_check_image
HEADリクエストを使用して画像のURLを検証します。
パラメータ:
url (str) - 検証する画像のURL
timeout_seconds (int, デフォルト: 10) - 接続タイムアウト
戻り値: 状態、コンテンツタイプ、サイズ(バイト)を含む辞書
QR/バーコードツール
⚠️ 重要提示
これらのツールにはlibzbarシステムライブラリが必要です。インストール方法は、macOSの場合はbrew install zbar、Linuxの場合はapt install libzbar0です。
decode_qr
画像ファイルからQRコードをデコードします。
パラメータ:
file_path (str) - 画像ファイルのパス
戻り値: 検出結果、数、コードのリストを含む辞書
decode_barcode
画像ファイルからバーコード(EAN、UPC、Code128など)をデコードします。
パラメータ:
file_path (str) - 画像ファイルのパス
戻り値: 検出結果、数、コードのリストを含む辞書
decode_all
画像ファイルからすべてのQRコードとバーコードをデコードします。
パラメータ:
file_path (str) - 画像ファイルのパス
戻り値: 検出結果、数、コードのリストを含む辞書
decode_and_annotate
コードをデコードし、バウンディングボックス付きの注釈付き画像を保存します。
パラメータ:
file_path (str) - 入力画像のパス
output_path (str) - 注釈付きの出力画像のパス
戻り値: 検出結果、数、出力パス、コードのリストを含む辞書
画像分析ツール
image_get_metadata
画像ファイルから寸法、形式、EXIFデータを含むメタデータを抽出します。
パラメータ:
file_path (str) - 画像ファイルのパス
戻り値: 幅、高さ、形式、モード、ファイルサイズ(バイト)、EXIF辞書を含む辞書
{
"width": 1920,
"height": 1080,
"format": "JPEG",
"mode": "RGB",
"file_size_bytes": 245678,
"exif": {"Make": "Canon", "Model": "EOS R5", ...}
}
image_get_stats
明るさ、コントラスト、シャープネスを含む基本的な画像統計を計算します。
パラメータ:
file_path (str) - 画像ファイルのパス
戻り値: 明るさ(0 - 1)、コントラスト(0 - 1)、シャープネス、グレースケールかどうかを含む辞書
{
"brightness": 0.65,
"contrast": 0.42,
"sharpness": 2.35,
"is_grayscale": false
}
image_get_histogram
各チャンネル(R、G、B)のカラーヒストグラムをオプションで可視化して計算します。
パラメータ:
file_path (str) - 画像ファイルのパス
output_path (str, オプション) - ヒストグラムの可視化を保存するパス
戻り値: チャンネル(256個の値のr、g、b配列)と出力パス(指定された場合)を含む辞書
image_get_dominant_colors
K-meansクラスタリングを使用して代表色を抽出します。
パラメータ:
file_path (str) - 画像ファイルのパス
num_colors (int, デフォルト: 5) - 抽出する色の数(1 - 20)
戻り値: RGB値、16進コード、割合を含む色のリスト
{
"colors": [
{"rgb": [64, 128, 192], "hex": "#4080C0", "percentage": 35.2},
{"rgb": [255, 255, 255], "hex": "#FFFFFF", "percentage": 28.1}
]
}
画像比較ツール
image_compare_ssim
構造的類似度指数を使用して2つの画像を比較します。
パラメータ:
file_path_1 (str) - 最初の画像のパス
file_path_2 (str) - 2番目の画像のパス
threshold (float, デフォルト: 0.95) - 類似度の閾値
戻り値: SSIMスコア(-1から1)、類似しているかどうか、閾値を含む辞書
{
"ssim_score": 0.9823,
"is_similar": true,
"threshold": 0.95
}
image_compare_mse
平均二乗誤差を使用して2つの画像を比較します。
パラメータ:
file_path_1 (str) - 最初の画像のパス
file_path_2 (str) - 2番目の画像のパス
戻り値: MSE、同一かどうか、正規化されたMSE(0 - 1)を含む辞書
image_compare_hash
知覚ハッシュを使用して2つの画像を比較します。
パラメータ:
file_path_1 (str) - 最初の画像のパス
file_path_2 (str) - 2番目の画像のパス
hash_type (str, デフォルト: "phash") - ハッシュタイプ: "phash"、"dhash"、または "ahash"
戻り値: ハッシュ1、ハッシュ2、距離、類似しているかどうか、ハッシュタイプを含む辞書
{
"hash_1": "8f0f0f0f0f0f0f0f",
"hash_2": "8f0f0f0f0f0f0f0f",
"distance": 0,
"is_similar": true,
"hash_type": "phash"
}
image_get_hash
単一の画像の知覚ハッシュを生成します。
パラメータ:
file_path (str) - 画像ファイルのパス
hash_type (str, デフォルト: "phash") - ハッシュタイプ: "phash"、"dhash"、または "ahash"
戻り値: ハッシュ(16進文字列)とハッシュタイプを含む辞書
image_diff
2つの画像の差異を強調したビジュアル差分を作成します。
パラメータ:
file_path_1 (str) - 参照画像のパス
file_path_2 (str) - 比較画像のパス
output_path (str) - 差分の可視化を保存するパス
threshold (int, デフォルト: 30) - ピクセル差分の閾値(0 - 255)
戻り値: 状態、出力パス、差分の割合、差分ピクセル数を含む辞書
{
"status": "success",
"output_path": "/path/to/diff.png",
"diff_percentage": 12.5,
"diff_pixels": 25600
}
image_compare_histograms
カラーヒストグラムによよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよよ