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 は出発点となる設定をディスクに書き出して開きます -- すべてのコントロールタイプを試す例であり、自分のアプリケーションに必要な設定だけに絞り込めるようになっています。

The OpenMV Cam Settings Editor showing the default demo configuration -- Camera, Processing, Network, and System tabs, with the Camera tab's exposure and gain sliders, resolution and pixel-format dropdowns, mirror and flip checkboxes, and a digital-zoom slider, above the Save and Cancel buttons

デフォルト設定を開いた状態の設定エディター -- 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.2. すべてのコントロールに共通するキー#

すべてのコントロールオブジェクトは、その type に関わらず次のキーを受け付けます:

  • type*(文字列、必須)-- 構築するウィジェットを指定します。例:``"slider"`` や ``"combobox"``。*type が欠落しているか認識されない場合は、ファイル全体を失敗させる代わりに無効化された Unknown control プレースホルダーを描画します。そのため入力ミスがあってもフォーム全体ではなくコントロール1つだけで済みます。

  • name*(文字列)-- *あなた自身 が使うための識別子です。エディタはこれを読み取らず、保持するだけです。各コントロールに一意な name を付ければ、スクリプトはそれを使ってそのコントロールの保存値を見つけられます。

  • label(文字列)-- コントロールの横または上に表示されるテキストです。ほとんどのコントロールではこれはリッチテキストで -- HTML タグや <a href> リンクが使用できます。

  • tooltip(文字列)-- コントロールのホバー時に表示されるテキストです。

  • enabled(真偽値、デフォルト true)-- false に設定すると、コントロールがグレー表示かつ読み取り専用になります。

コンテナコントロール(grouptabs)は見出しに label ではなく title を使い、tabs はタブごとにツールチップを受け取ります。相違点は各コントロールのページに記載されています。

13.1.13.3. 値の保存と読み取り#

値を保持するコントロール -- チェックボックス、スライダー、テキストフィールド -- は、それを value キーに保持します。Save をクリックすると、エディタは各コントロールの現在の value を同じコントロールオブジェクトへそのまま書き戻し、ファイルを書き直します。ファイルの構造は維持されます:何も移動せず、value キーだけが変わります。name から値への別個のフラットなマップは存在しません -- 値はコントロールの内部、まさに定義された場所に保持されます。

だからこそ、レイアウトがスクリプトにとって重要になります。controlstabs の配列は構築した順序を保つため、ファイルは辞書のように名前でインデックス参照できません -- 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. 設定ファイルリファレンス#

コントロールタイプは、その働きごとにグループ化して以下のページで説明します。