13.1.13. 設定エディタ#
多くのカメラアプリケーションでは、スクリプトに触れることなく、プログラマーでない人が変更できる少数の設定 -- しきい値、モード、Wi-Fi パスワード -- が必要です。OpenMV Cam Settings Editor はそのための IDE のツールです: JSON ファイルがコントロールを記述し、エディタがその記述をフォームに変換し、スクリプトは json.load() で保存された値を読み戻します。ファイルは構成であると同時に、それ自身のエディタの定義でもあるため、値を格納するのと同じ構成が、それらを編集するための GUI もレイアウトします。
Tools → OpenMV Cam Settings Editor サブメニューには、その 3 つのアクションが含まれています。
Open OpenMV Cam Settings Config File は、接続されたカメラのドライブから構成を読み込み、エディタで開きます。保存するとカメラに書き戻されます。
Open Config File は、オフラインで構成を準備するために、お使いのコンピュータ上の
.jsonファイルに対して同じことを行います。Create Default Config は出発点となる設定をディスクに書き出して開きます -- すべてのコントロールタイプを試す例であり、自分のアプリケーションに必要な設定だけに絞り込めるようになっています。

デフォルト設定を開いた状態の設定エディター -- JSON ファイルのコントロールがフォームとして配置され(タブ、スライダー、ドロップダウン、チェックボックス)、下部に Save と Cancel が配置されています。#
13.1.13.1. 設定ファイルの構造#
設定ファイルは、次の2つのキーを持つ単一の JSON オブジェクトです:
title(文字列、省略可)-- エディタウィンドウのタイトルです。デフォルトは
OpenMV Cam Settings Editorです。controls(配列、必須)-- エディタがフォームの行として順に描画するコントロールオブジェクトのリストです。これが GUI 全体になります。
{
"title": "Camera Settings",
"controls": [
{ "type": "slider", "name": "threshold", "label": "Threshold", "value": 50 },
{ "type": "checkbox", "name": "draw_overlays", "label": "Draw Overlays", "value": true }
]
}
controls の各エントリは1つのコントロールです -- テキストフィールド、数値、チェックボックス、ドロップダウン、スライダー、group、タブストリップ、静的ラベルなど。その type がウィジェットを選択し、オブジェクトの残りのキーがそれを設定します。エディタが認識しないキーはファイル保存時にそのまま残されるため、独自のメタデータをコントロールと並べてファイル内に保持できます。
13.1.13.3. 値の保存と読み取り#
値を保持するコントロール -- チェックボックス、スライダー、テキストフィールド -- は、それを value キーに保持します。Save をクリックすると、エディタは各コントロールの現在の value を同じコントロールオブジェクトへそのまま書き戻し、ファイルを書き直します。ファイルの構造は維持されます:何も移動せず、value キーだけが変わります。name から値への別個のフラットなマップは存在しません -- 値はコントロールの内部、まさに定義された場所に保持されます。
だからこそ、レイアウトがスクリプトにとって重要になります。controls と tabs の配列は構築した順序を保つため、ファイルは辞書のように名前でインデックス参照できません -- config["controls"]["threshold"] は機能しません。この小さなヘルパーがその隙間を埋めます。目的の値までの名前のパスを渡せば、ツリーをたどってくれます。
import json
def find(node, *path):
"""Return the control at a path of names; read its value with
["value"]. The tab strip and any unnamed group are transparent,
so you list only the tabs and controls you gave a name."""
for key in path:
match = _child(node, key)
if match is None:
raise KeyError(key)
node = match
return node
def _child(node, key):
for member in node.get("controls", []) + node.get("tabs", []):
if member.get("name") == key or member.get("title") == key:
return member
if "name" not in member and ("controls" in member or "tabs" in member):
match = _child(member, key) # see through an unnamed group / the tab strip
if match is not None:
return match
return None
with open("config.json") as file:
config = json.load(file)
threshold = find(config, "threshold")["value"]
パスの各段階は、コントロールの name またはタブの title です(タブはそれ自体の name を持ちませんが、追加すれば保持されます)。タブストリップと名前を付けなかった group は透過的で -- find はそれらを通り抜けます -- そのため、タブと名前を付けたコントロールだけを列挙すればよく、ネストされた config[...][...] の参照に近くなります。名前付きの group はそれ自体が一段階になるため、find はそのチェックボックスの値を返すこともできます。パスが決して曖昧にならないよう、すべてのコントロールに一意な name を付けてください。完全な例 は、ネストされたデモレイアウトから値を読み取ります。
設定は素の JSON であるため、スクリプトはエディタに一切依存しません -- エディタは、スクリプトがすでに読み取っているファイルを書き出すだけです。デバイスは自身の設定と、それを変更する分かりやすい手段を備えることができ、その一方でスクリプトは単なる値の読み取り役にとどまります。
1つのコントロールタイプは Save 時に入力を強制します:mask または regex を持つ lineedit は、そのテキストが無効または不完全な間はエディタによる保存を拒否し、修正すべきフィールドを示します。他のすべてのコントロールは編集中に入力を制約します -- スライダーは範囲外に出られず、spinbox は境界に丸められます -- そのため保存されたファイルは常に有効です。
13.1.13.4. 設定ファイルリファレンス#
コントロールタイプは、その働きごとにグループ化して以下のページで説明します。