目的 = Fullseye Studio の Program を「線形 (op,a,b) パイプラインにスクリプトの皮を被せたもの」から、 HALCON HDevelop 級の本物のプログラミング環境へ進化させる。ユーザー確定の方向性(2026-08-15 対話):
- 専用言語(HDevEngine 構文が第一希望、C/C++ 風でも可)で、名前付き変数 + 実 if/for/while(測定値で分岐)+ per-object 反復 + I/O。
- Fullseye = 画像処理ライブラリ、言語はそれを呼ぶ層(ロジックを言語に内蔵しない)。
- コンパイラ方式が望ましい(インタプリタより)。
- 厳密なウォッチ: 変数ウォッチに加え 画像ウォッチ / Region ウォッチ / 画像の特定ドメイン(ROI)ウォッチ。
- 最終的には Fullseye を DLL 化するのが一番自然。
- ロボット制御プログラムまで書けること(知覚→判断→動作のループ)。
外部 AI(Codex read-only)設計コンサルト済(20 項目、本書に反映)。規律=鵜呑み禁止、コードで裏取り。
結論(Codex #1): 言語を主、線形パイプラインは「分岐・副作用の無い直列部分集合(LinearSubset)」として残し、進化・高速化・旧 UI に再利用。
ユーザー洞察=「Linux でも使うなら、かなり独自の基盤を作った上に言語を載せる形では?」→ 正しいが、基盤は段階で変わる:
.dll / Linux .so / 組込へクロスコンパイル、メモリ安全、PyO3 で Python 相互運用、C ABI 公開)。性能/配布のオプション層で op ごとに段階追加、L1 API 契約固定で L2 言語を変えず差し替え。ユーザー洞察=「Python 用の開発プラットフォームに画像処理用の便利なウォッチ機能がついている形でも全然あり」。これはむしろ第一目標として賢明。次セッションの要件定義はまずこの A/B を決める。
sys.settrace or 制御 exec + 変数捕捉 + §4 型別レンダラ。
┌─────────────────────────────────────────────────────────────┐
│ L3 Studio IDE … エディタ / 実行(debug|run|profile) / │
│ ★ウォッチパネル(§4) / ブレークポイント / │
│ 実行カーソル / Variable & Object 窓 │
├─────────────────────────────────────────────────────────────┤
│ L2 Fullseye Script 言語 … lexer→parser→typed AST→ │
│ bytecode compiler→VM(=コンパイラ)。ソース位置を第一級。 │
│ 制御フロー・変数環境・per-object 反復・例外・キャンセル。 │
│ ★ロジックは持たない。L1 を呼ぶだけ(LanguageOperatorSpec)。│
├─────────────────────────────────────────────────────────────┤
│ L1 Fullseye ライブラリ … 実パラメータのビジョン/計測/幾何/ │
│ デバイス関数(read_image/gauss/threshold/connection/ │
│ area_center/…/comm/device/acquire)。numpy/scipy 実装。 │
│ ★将来: ホットパスを C codegen → ネイティブ DLL(C ABI)。 │
│ 進化エンジンの正規化ノブ registry とは契約を分離(§3)。 │
└─────────────────────────────────────────────────────────────┘
fslib.py(+ fullseye パッケージから再 export)。将来 DLL: 同じ関数群を C ABI(fullseye_threshold(img, lo, hi, out) 等)で公開し、Python 側は cffi/PyO3 で薄くバインド。API 契約(型・単位・出力)を今から DLL 化を見据えて固定しておく(移行時に言語 L2 を変えずに差し替え可能に)。LOAD_VAR/CALL_OP/STORE_VAR/JUMP_IF_FALSE/ITER_OBJECTS/CALL_DEVICE)。AST 直接評価は増分 1 の踏み台(意味論検証)で、早期に bytecode へ移行。ExecutionEvent(span, changed_vars, removed_vars, pc, state) を発火 → ウォッチ/変数窓を差分更新(Codex #12)。operator (InIconic : OutIconic : InControl : OutControl)。区画は省略可だがコロン位置は維持。
read_image (: Image : 'samples://images/parts_01.png' :)
threshold (Image : Region : 0.42, 1.0 :)
connection (Region : Objects : :)
area_center (Object : : : Area, Row, Column)
★互換のため増分 1 では assignment 形 Out := op(In, ctrl…)(C/C++ 風・universal)も許可し、4 区画は op signature 整備と共に段階導入。
Image/Region/XLD + ObjectSet(反復可能な iconic 集合)/ control=number/string/bool/tuple/handle。iconic↔control の暗黙混同は禁止(代入で sort 記録)。numpy 配列を tuple 扱いしない。Image = FImage(matrix, domain: Region) とする。
reduce_domain(Image, Region : ImageReduced : :) で domain を制限、full_domain(Image) で全面化、get_domain(Image : Region : :) で domain を Region として取り出す。threshold/connection 等の出力 Region は入力 Image の domain と交差。これで「ROI に限定した検査」が第一級表現になり、§4 の domain ウォッチ(image の domain を見る)がネイティブに成立。domain=None=full)。reduce_domain 時のみ Region を保持。DLL 化時も FImage の C ABI(pixels ptr + domain run-length)で表現。:=(代入)/ =(比較)/ # != <= >= < > / and or not / + - * / mod / tuple [..]・t[i]・|t|・連結 / 括弧。未実装構文は黙って解釈せず構文エラー。['part', 5, 3.14, 'ok'])。→ Fullseye も control 値を 異種混在 Tuple(要素 = int / real / string、bool は int)としてモデル化。
Tuple は不変値。要素型は保持(整数 5 と実数 5.0 を区別)。t[i](0-based)・|t|(長さ)・[t1, t2](連結。+ は要素和、§2b)・subset/remove/insert/tuple_gen_const。算術/比較は要素ごとにブロードキャスト(HALCON 準拠、長さ 1↔N・N↔N)。+ は 数値タプル同士=要素和、文字列を含む=連結 の HALCON 曖昧規則を踏襲(仕様書に明記、. を文字列連結専用にするかは要検討)。空タプル []、混在時の型昇格規則(int→real)を定義。fscript.py)は control を素の float/int/str/list で扱う。増分1の型システムを HALCON Tuple に置換(Tuple クラス + 要素型保持 + broadcast 演算)する必要がある。vector(dim)、Vec.at(i)、|Vec|、代入 Vec[i] := ...、for で反復。Tuple(平坦・混在スカラ)と Vector(入れ子・object も格納可)を別型として用意する。
FVector(要素型 + 次元を保持) に厳密化。ウォッチは §4 の型別レンダラで Vector(要素を展開表示)対応。list(可変長)・map/dict(連想)・set・stack・queue/deque・pair。iterator/for-each、push/pop/insert/erase/find/size/empty 等の STL 相当メソッド。FContainer 群(内部は Python dict/list/set 等を薄くラップ、型付き)。DLL 化時は C++ STL / 相当の C ABI で対応。ウォッチは型別レンダラ(map=キー/値表、set/stack/queue=要素列)。map、処理待ち ROI の queue、状態機械の stack(ロボット制御シーケンス)、ユニーク特徴の set。デザインパターン(State/Strategy/Observer 等)を素直に書ける土台。int(x)/real(x)/number(x)/is_number/is_int/is_string、tuple 要素の型判定 type_of(t[i])、int↔real 昇格規則。HALCON の暗黙昇格 + 明示変換の両立。string(x, fmt)(数値→書式付き文字列、HALCON '$.3f' 風)/str_to_number/split/join/regexp_match/replace/length/部分文字列/str_upper/lower/連結。ロボット制御の I/O(コマンド組立/パース)や結果出力に必須。if/elseif/else/endif・for V := a to b [by s]/endfor・for Obj in Objects/endfor・while (c)/endwhile・repeat/until (c)・break/continue。
for Obj in Objects は未実装(_KEYWORDS に in が無く、パーサは for V := a to b のみ受理)。
現状の per-object 反復は for I := 0 to N-1 + select_obj(Objects, I) で書く。for ... in は増分 1 の追加項目。
未実装構文が構文エラーになること自体は正しい挙動(tests/test_fscript.py で固定)。停止性: VM に命令予算 + wall-clock deadline + キャンセルフラグ、while/repeat は定期 UI ポンプ。area_center(Region : : : Area, Row, Column) の複数 control 出力を正式サポート(現 api.apply は先頭要素のみ float 化=情報欠落)。空領域/NaN/長さ不一致は定義済み例外か空 tuple、条件式で暗黙真偽化しない。fscript.py)の確定挙動 —— 仕様と実装を一致させる(2026-09-03 監査で確定)上の §2 は北極星(設計)。現在の実装が実際にどう振る舞うかは次の表が正本で、tests/test_fscript.py /
tests/test_fsruntime.py が 1 項目ずつ固定している。「黙って誤答しない」の規律で、迷う入力は全て FScriptError(行番号つき)。
| 項目 | 確定挙動 |
|---|---|
| グレー値の単位 | 言語のグレー値は 画像の宣言レンジに対する割合(0 = レンジ下端、1 = 上端)。threshold(Image, lo, hi) も mean_gray/min_gray/max_gray も同じ単位。8-bit 画像の画素 128 は 0.502 と読む。∴ threshold(Image, mean_gray(Image), max_gray(Image)) は 8-bit でも float でも同じ意味(以前は統計だけ生画素値で、8-bit では面積 0 を黙って返した)。 |
| タプル算術 | + - * / % と単項 - は 全て要素ごと(長さ 1↔N ブロードキャスト、N↔N は等長必須、違えばエラー)。[1,2] * 2 = [2,4](Python の繰り返し [1,2,1,2] ではない)。連結は [t1, t2]。 |
| 文字列の算術 | 文字列 + 文字列 = 連結のみ。'ab' * 3、'a' + 1 はエラー(Python の繰り返し/型混在は言語機能ではない)。 |
| スカラ = 長さ 1 のタプル | [1] = 1 は真、if ([0]) は偽、not [0] は真。長さ ≠ 1 のタプルは条件式に置けない(エラー)。比較 < > <= >= でタプルとスカラを混ぜるとエラー、=/# はタプル全体の等価。 |
| 添字 | t[i] / s[i] の i は 0 以上の整数(2.0 のような整数値の実数は可、1.9・負数・文字列はエラー)。範囲外はエラー(index 3 out of range (length 3))。負の添字は無い。 |
| 添字代入 | Name[i] := expr を実装(expr はスカラ。タプルは平坦なので入れ子不可)。タプルは値: B := A はコピーで、B[0] := 9 は A に届かない。images= で渡した Python list も複製され、スクリプトは呼び手のリストを書き換えない。 |
| 数値リテラル | ASCII 数字のみ: 12 / 1.5 / 1. / .5 / 1e-3 / 2.5E+4。1.2.3 / 2e / 1e5e3 / 3(全角)/ ² は構文エラー。1e400 のような無限大に落ちるリテラルもエラー。 |
| 文字列リテラル | '...' は 1 行に収まる(行をまたぐと unterminated string)。エスケープは \' と \\ だけ。それ以外のバックスラッシュはそのままの文字('<ローカルの作業パス>\images\a.png' はそのまま読める。'<ローカルの作業パス>\dir\' は \' がエスケープになり未終端エラー → '<ローカルの作業パス>\dir\\' と書く)。 |
break / continue |
ループの外では構文エラー('break' outside loop)。 |
| 比較の連鎖 | 0 <= X <= 10 は禁止(エラー)。括弧で囲んだ比較は明示のオペランドなので (X > 3) = true / (1 < 2) = (2 < 3) は可。 |
| 条件ヘッダ | if/elseif/while/until の条件は行末まで。if (X = 1) or (Y = 1) は 1 つの条件。(...) で始まるヘッダは and/or でしか続けられず、if (X = 1) -1 や for I := 0 to 2 X := I(ヘッダ行に文)は unexpected ... after statement。 |
for の境界 |
start/stop/step は数値(文字列・長さ ≠ 1 のタプルはエラー)。step 0 はエラー。 |
dilation / erosion / mean_image の半径 |
0 以上の整数(画素)。0 は恒等(領域をそのまま返す)、負・小数(0.4)はエラー(以前は max(1, int(r)) で全部 1 に化けていた)。 |
| op の引数 | 数値引数に文字列やタプル(長さ ≠ 1)を渡すと ... must be a number, got string 'x' の FScriptError(素の ValueError は出ない)。エラーは必ず呼び出し行を持つ。 |
read_image のパス |
base_dir があれば その配下に閉じ込める(相対は base_dir 基準、.. は解決後に判定、絶対パスも配下のみ)。外なら path ... is outside the script's base directory。base_dir 無し(呼び手が明示的に省略)の場合のみそのまま開く。industrial プロファイルの Runtime は read_image を含むレシピをロードで拒否(サイクル内ファイルアクセス禁止 = FSCRIPT_DECISION.md §3.1 R4。フレームは images= で渡す)。 |
| ネストの上限 | 括弧/単項/ブロックの入れ子と、評価時の式の深さは 200 まで(nesting too deep (limit 200))。1+1+…+1 を 200 項以上連ねた式も同じエラー。Python の RecursionError は外に出ない。check() は決して例外を投げず文字列で返す。 |
golden の digest(fsruntime) |
manifest digest は repr() ではなく正準バイト列(型タグ + 長さ + float.hex() / dtype.str + shape + tobytes())。np.printoptions に依存せず、1000 要素超の配列の 1 要素差も検出する。GoldenVector.expect に載せられる型は bool/int/float/str/それらの list・tuple/numpy 配列のみ(他は構築時 TypeError)。旧 digest と互換性は無い(署名済みレシピは sign で再署名)。 |
RT[name](v,a,b) で公開しない。LanguageOperatorSpec(name, in_iconic, out_iconic, in_control=[Param(name,type)], out_control, invoke) を別レイヤに置く。normalized_knobs と semantic_parameters を別契約に。ObjectSet = ラベル画像 + object ID 列 + 遅延 view(mask はコピーせず必要時 materialize、copy-on-write)。connection が生成、select_obj/concat_obj/count_obj/for Obj in Objects。legacy_apply(Image : Result : A, B :) に警告付き隔離(一括で HDevelop 互換と称さない)。fslib.py」と書いていたが、fslib.py は存在しない。
上記の語彙は fscript.py 内の _b_* 関数 + BUILTINS 辞書として実装されており、L1 と L2 は未分離。
「L1 を fslib.py へ分離する」は済んだ作業ではなく、増分 1 の未着手タスクである。ユーザー要件 = 変数ウォッチだけでなく 画像ウォッチ / Region ウォッチ / 画像の特定ドメイン(ROI)ウォッチ。デバッガの中核。
Watch(expr, kind, options)。ウォッチは型ごとに拡張可能なレンダラ登録制(WatchRenderer レジストリ)= 新しい iconic/handle 型を足したら renderer を 1 つ登録すれば IDE が対応。ユーザー指摘「HALCON には Region 以外のオブジェクトもある、それも全てウォッチ必須」に対応。
kind=control … 数値/文字列/tuple の値(履歴リング + 変化グラフ)。kind=image … 画像(+ domain)。表示=サムネイル + ピクセル統計(min/max/mean/hist) + ズーム可能ビュー + domain 境界 overlay。オプション: colormap、値レンジ。kind=region … Region(マスク)。表示=元画像上に色 overlay(margin/fill) + 面積/重心/bbox。kind=xld … XLD(サブピクセル輪郭/ポリゴン)。表示=元画像上に輪郭 polyline overlay + 制御点数/長さ/曲率。HALCON の XLD_cont/XLD_poly に相当(imgevolve は contour(XLD) sort を既に持つ)。kind=domain(ROI) … 画像の特定ドメインをウォッチ: Watch(Image, domain=Rect(r1,c1,r2,c2)) または Watch(Image, domain=Region)。表示=その ROI に限定した crop + 統計(ROI 内 mean/hist/欠陥率)。HALCON の Image が内包する domain(§3)そのものを非破壊観測。kind=objectset … ObjectSet(任意 iconic 型の集合、Region に限らない)。表示=object 数 + 各 object を型別 renderer でサムネイル + 特徴表。kind=handle … 不透明ハンドル(matching model / measure / classifier / calibration / OCR 等)。表示=ハンドル型名 + 主要パラメータ + 生成元(内部生データは出さない)。HALCON の handle 群に対応。register_watch_renderer(type, renderer) で新型を追加(volume/point-cloud/mesh 等 imgevolve 固有の iconic も同機構でウォッチ可能に)。watch Image, watch Region as overlay, watch Image[Rect(10,10,50,50)], watch |Objects| を Studio UI(右クリック→Watch / Watch パネルに式追加)で管理。式は L2 のサブセット(副作用なし)に限定。stage list → script は常に可能。script → stage list は LinearSubset(単一 iconic in/out・直列・定数 control・分岐/ループ/I-O/複数出力なし)のみ。変換不能は行番号付きで提示、勝手にフラット化/分岐選択しない。EvolvableBlock のみ生成(6 slot×(op,a,b) 固定長を維持)。外側の言語が取得/分岐/測定/動作、block 内だけ既存ゲノム + train 選択 + holdout/locked-holdout。
evolve_block Denoiser (Input : Output)
gauss_filter (Input : T1 : 0.63 :)
threshold (T1 : Output : 0.47, 1.0 :)
endblock
open_camera/grab_image/close_camera(acquire.py)、Modbus read/write(comm.py)、DigitalIO set/get/pulse/wait_input(device.py)を LanguageOperatorSpec 登録。on_error/finally 自動 close、単調時計 deadline、wait_input(...,timeout)、キャンセル可能 delay、simulation backend、動作範囲/速度/出力 pin の allowlist、Studio 初回 arm 確認。画像 worker と device I/O worker を分離、VM は逐次意味論を保ち future 完了待ち。→「取得→測定→条件分岐→動作→センサ確認→timeout」ループを安全記述。samples/
scripts/ 01_threshold_and_measure.fsh … # サンプルコード(専用フォルダ)
images/ parts_01.png defects_scratches.png … # 合成画像(手続き生成)
generators/ make_inspection_samples.py # 生成器(seed/条件)
manifests/ samples.json # 生成条件・期待結果・ライセンス
read_image 解決規則(決定的・安全): samples://images/x.png=install 済 sample root、相対=呼出しスクリプトの親、絶対=許可 workspace 内。cwd 依存禁止、path traversal 検査、拡張子制限(Codex #15)。fscript.py(意味論検証)+ L1 fslib.py(実装済み種)+ 実 if/elseif/else・数値 for・object for・代入・式・per-object 反復。「blob 検出→per-object 面積→閾値除外→重心」実アルゴリズムが走ることをテストで実証。samples 生成器 + 2 スクリプト。while/repeat、診断。各段階で parser golden tests / 型エラー / 空 object / キャンセル / 同一 seed 画像 / 旧 pipeline parity を回帰化。
fscript.py(増分 1・AST インタプリタ): lexer/parser/AST/evaluator + 変数環境 + 実 if/for/while/repeat + 例外 + step 予算。
L1 の builtin(_b_* / BUILTINS)も同一ファイル内にあり、L1/L2 は未分離。tests/test_fscript.py(本セッション新規): 22 passed / 5 xfail。それ以前は fscript のテストは 1 件も存在しなかった
(「smoke test 実証」は ad-hoc 実行であってコミットされた回帰テストではなかった — 正直な訂正)。docs/FSCRIPT_MEASUREMENTS.md(サイクルタイム / ジッタ / コールドスタート / 配布サイズ / 欠陥 5 件)。docs/FSCRIPT_MEASUREMENTS.md の結論:
* のコメント誤認 = 修正済、値域の内容依存正規化、Tuple +、
iconic の暗黙真偽化、比較の .any() 潰れ)。すべて 実装言語を変えても残る意味論の問題。
→ 「まず型システム、次に VM」。ネイティブ化はその後。