Fullseye は vision に加え デバイス制御・産業通信 を扱う(HALCON に無い差別化)。
下表は comm.capabilities() / acquire.capabilities() / device.capabilities() が
返す実カタログを書き写したもの。この doc は手書きで、正しさは門が持つ ——
表と実装がずれたら tests/test_connectivity_doc.py が落ちる。
3 層とも同じ約束を守ります(protocol / image source / driver):
comm.protocols() / acquire.capabilities() / device.drivers()open_channel / open_framegrabber / open_driver3 つそろって初めて「対応している」と言えます —— 名簿だけ在って開け方が無い状態は、
読んだ人をそこで止めるからです(tests/test_acquire_contract.py の
test_the_three_connectivity_layers_make_the_same_promise が 3 層まとめて見ます)。
pip 欄のパッケージを入れると有効になるopen_* が口を返す)。— の行は名簿には載るが、
その SDK を直接使う —— open_* は何を入れればよいかを名指しで返します。
★この欄は版で決まるので表に書けます(「SDK が入っているか」は機械ごとに違うので、
表には書かず capabilities() の available で見ます)。| protocol | kind | 実装 | pip | 説明 |
|---|---|---|---|---|
| http | native | ✓ | — | HTTP/REST client |
| modbus-tcp | native | ✓ | — | Modbus TCP client (PLC / I-O; built-in, no deps) |
| tcp | native | ✓ | — | raw TCP client socket |
| udp | native | ✓ | — | UDP socket |
| bacnet | optional | — | BAC0 | BACnet/IP (building automation) |
| can | optional | — | python-can | raw CAN bus |
| ethernet-ip | optional | — | pycomm3 | EtherNet/IP + CIP (Allen-Bradley Logix) |
| fins | optional | — | fins-driver | Omron FINS (CIO/DM areas) |
| modbus-rtu | optional | ✓ | pyserial | Modbus RTU on a serial line (built-in; pyserial for a real port) |
| mqtt | optional | — | paho-mqtt | MQTT pub/sub (IIoT broker) |
| opcua | optional | — | asyncua | OPC-UA client (industrial servers) |
| s7 | optional | — | python-snap7 | Siemens S7 (S7comm) — DB/Merker/I/O |
| serial | optional | ✓ | pyserial | RS-232/485 serial port (send/receive) |
| slmp | optional | — | pymcprotocol | Mitsubishi MC protocol / SLMP (MELSEC) |
| sparkplug | optional | — | pysparkplug | Sparkplug B over MQTT |
| websocket | optional | — | websocket-client | WebSocket client |
| zmq | optional | — | pyzmq | ZeroMQ messaging |
| cclink | scaffold | — | — | CC-Link IE (no pure-python master; reach via SLMP) |
| dnp3 | scaffold | — | pydnp3 | DNP3 / IEEE 1815 (SCADA/utility) |
| ethercat | scaffold | — | pysoem | EtherCAT master (RT NIC + slaves; the motion bus) |
| iec61850 | scaffold | — | pyiec61850-ng | IEC 61850 MMS/GOOSE (substation) |
| profibus | scaffold | — | pyprofibus | PROFIBUS DP (RS-485 PHY, GSD) |
| profinet | scaffold | — | pnio-dcp | PROFINET DCP commissioning (RT via gateway) |
unit = grab() が返す量。normalised = [0,1] の画像 / m = メートルの距離(深度は計測値なので [0,1] に正規化しない)。
開く = opener が在るか。★見つける = 装置一覧に出るか —— 別の問いなので別の列にしてある。2026-09-25 まで zed と kinect は開けるのに一覧に出てこず、1 列にまとめていたせいで表からは見えなかった。— は装置を持たない合成 backend(acquire.NOT_ENUMERABLE に理由つき)。あり = その SDK がこの環境で import できるか ——別の問いなので別の列にしてある(申告だけして開けない行が在った)。
| source | kind | unit | 開く | 見つける | あり | pip | 説明 |
|---|---|---|---|---|---|---|---|
| callable | native | normalised | ✓ | — | ✓ | — | a user-supplied fn() -> frame |
| dir | native | normalised | ✓ | — | ✓ | — | a folder / glob of images (offline & tests) |
| basler | optional | normalised | ✓ | ✓ | — | pypylon | Basler cameras (pypylon) |
| genicam | optional | normalised | ✓ | ✓ | — | harvesters | GigE/USB3 Vision via GenTL (industrial) |
| opencv | optional | normalised | ✓ | ✓ | ✓ | opencv-python | USB/UVC webcam, IP/RTSP stream, video file |
| vimba | optional | normalised | ✓ | ✓ | — | vmbpy | Allied Vision Vimba X (vmbpy) |
| kinect | optional | m | ✓ | ✓ | — | pyk4a | Azure Kinect DK depth (discontinued; Orbbec is the successor) |
| oak | optional | m | ✓ | ✓ | — | depthai | Luxonis OAK-D stereo depth (depth in metres) |
| realsense | optional | m | ✓ | ✓ | — | pyrealsense2 | Intel RealSense RGB-D (depth in metres) |
| zed | optional | m | ✓ | ✓ | — | pyzed | Stereolabs ZED stereo depth (depth in metres) |
分母は EMVA が公表している綴りの全数(GenICam Pixel Format Names and Values、無償)であって、こちらが知っている綴りの数ではない。自分の表から数えると、そもそも知らない形式は永遠に見つからない —— 実際この分母に替えて 10 bit 非詰めの Bayer 4 形式が抜けているのが出た。台帳は examples/data/pfnc_single_plane.json。
| 群 | 規格 | 対応 | 理由つきで外した | 対応率(外した分を除く) |
|---|---|---|---|---|
| Mono | 15 | 10 | 5 | 100% |
| Bayer | 44 | 40 | 4 | 100% |
外したものは acquire.NOT_CARRIED に理由つきで並ぶ(8 bit 未満 / 符号つき / 32 bit)。黙って知らないままにはしないのが要点で、「対応率」は 「外した理由が書いてある」ことと一緒でなければ意味がない。
詰め形式(Mono12p など 25 形式)は acquire.unpack() が展開する。詰めたままのバッファを 2**bits - 1 で割ると、例外を出さずに画像に見える別のものが出るので、_coerce は uint8 の詰めバッファを拒否する。
ExposureTime も TriggerMode も PixelFormat も、GenICam SFNC が綴りを決めた規格の名前であってベンダの名前ではない。だから 1 本の語彙表で GenTL を出す全ベンダを覆える —— ベンダ SDK を 1 本足しても覆えるのは 1 社。台帳は examples/data/sfnc_acquire_vocabulary.json(SFNC v2.8 から)。
| 数 | |
|---|---|
| 規格が必須と決めている機能 | 22 |
| 任意の機能 | 9 |
cam = acquire.Camera(0, backend="genicam", cti=["/path/to/producer.cti"])
cam.configure(ExposureTime=5000.0, TriggerMode="Off") # us は SFNC 1.2 が決めた単位
cam.missing_required_features() # 規格に足りない機能があれば出る
★configure() は装置が実際に取った値を読み直して返す。カメラは clamp する(上限 33000 us の機に 100000 を頼めば 33000 になる)ので、要求値を返すのは小さな嘘になり、そのまま露光の取り違えになる。
★単位は推測しない。ExposureTime は us、AcquisitionFrameRate は Hz、PayloadSize は B、TimestampLatchValue は ns。Gain に単位の規定は無い(dB は §1.2 の一覧に載っているだけ)ので、この層は Gain を dB と呼ばない。
opencv backend が開いているのは USB Video Class (UVC) 1.5 の装置で、この規格も
GenICam SFNC と同じように制御の名前を決めている。台帳は examples/data/uvc_controls.json
(UVC 1.5 の本文から)。
| 数 | |
|---|---|
Camera Terminal CT_* |
21 |
Processing Unit PU_* |
19 |
Video Streaming VS_* |
9 |
acquire.uvc_to_sfnc("CT_EXPOSURE_TIME_ABSOLUTE_CONTROL", 5000) # -> ("ExposureTime", 500000.0)
acquire.sfnc_to_uvc("ExposureTime", 500000.0) # -> (..., 5000)
★2 つの規格で露光の単位が違う。UVC の dwExposureTimeAbsolute は本文が
「1: 0.0001 sec … 100000: 10 sec」と定める 0.0001 秒(100 us)刻み、SFNC の
ExposureTime は us。同じ 5000 が 5 ms と 0.5 秒になる —— 100 倍で、例外は
出ず絵の明るさだけが変わる。だから換算は表に書き、門が往復で確かめる。
★単位は本文が言っているものだけを運ぶ。焦点は mm、絞りは f 値 × 100、
パン・チルトは秒角。Gain は UVC も SFNC も単位を規定していないので、この層は
どちらの側でも dB と呼ばない。
公開されている 13 の Python SDK を採点したのと同じ 8 軸で、この層自身を採点する。違う物差しで測った数を並べると「SDK より厚い」が意味を失う。
★自己申告にしない。各軸はどの入口で満たされているかを実在する名前で名指しし、門がその名前を引く。改名や削除があれば落ちる —— 証拠が消えた「対応済み」は嘘になる。
| 軸 | 充足 | 入口 |
|---|---|---|
| 列挙 | ✓ | list_devices・_enumerate・gentl_producers |
| 取得モード | ✓ | Camera.grab・Camera.frames・Camera.stream・Camera.grab_frame |
| バッファ | ✓ | Camera._raw_grab |
| 画素形式 | ✓ | PIXEL_BITS・PACKED_FORMATS・NOT_CARRIED・unpack |
| ビット深度 | ✓ | bit_depth_of・_to01 |
| メタデータ | ✓ | Frame・Camera.features |
| 物理単位 | ✓ | DEPTH_BACKENDS・SFNC_FEATURES |
| 終了処理 | ✓ | Camera.close |
比較の相手は repo の外に置いた device SDK コーパスの採点表(公表されている 13 の Python SDK を同じ 8 軸で読んだもの)。そこでは pypylon と vmbpy が 8/8、ouster / pyrealsense2 / zivid が 7/8 だった。
| driver | kind | 種別 | 実装 | pip | 説明 |
|---|---|---|---|---|---|
| io-memory | native | io | ✓ | — | in-process digital I/O (tests / dry-run) |
| io-modbus | native | io | ✓ | — | digital I/O over Modbus coils (built-in) |
| canopen | optional | motion | — | canopen | CANopen CiA-402 motion drives |
| dynamixel | optional | servo | — | dynamixel-sdk | Robotis Dynamixel servos |
| feetech | optional | servo | — | feetech-servo-sdk | Feetech STS/SCS servos |
| gpio | optional | io | ✓ | python-periphery | SBC GPIO — Raspberry Pi / Jetson (also RPi.GPIO / gpiod) |
| robotiq | optional | gripper | — | pyRobotiqGripper | Robotiq 2F / Hand-E grippers |
| ros | optional | middleware | — | rclpy | ROS 2 node bridge (rclpy) |
| ur-rtde | optional | robot | — | ur_rtde | Universal Robots RTDE / URScript |
| xarm | optional | robot | — | xArm-Python-SDK | UFACTORY xArm / Lite6 / 850 |
| franka | scaffold | robot | — | panda-python | Franka Panda / FR3 (libfranka + RT kernel) |
| kinova | scaffold | robot | — | — | Kinova Gen3 (off-PyPI kortex wheel) |
import fullseye
# 検査 → PLC ハンドシェイク(ハード無しでも simulator でテスト可)
srv = fullseye.ModbusTcpServer(port=1502).start() # 開発用シミュレータ
io = fullseye.DigitalIO("modbus", host="127.0.0.1", port=1502)
ok = fullseye.FullseyeEngine.from_ops("gaussian,otsu").run(frame).mean() > 0.5
fullseye.signal_result(io, ok) # PASS/FAIL 出力を駆動
fullseye.pulse(io, pin=3, ms=50) # リジェクト射出
上の表の driver 名をそのまま渡して開けます。開けないものは「何を入れれば よいか」を名指しで返すので、名簿を読んでから手が止まりません。
fullseye.drivers() # 名簿(表と同じ名前)
io = fullseye.open_driver("io-memory") # ハード無しで動く(試験・素振り)
io = fullseye.open_driver("io-modbus", host="127.0.0.1", port=1502)
try:
arm = fullseye.open_driver("ur-rtde")
except fullseye.DeviceError as e:
print(e) # driver 'ur-rtde' needs 'rtde_control' (pip install ur_rtde)
名簿に在るが開けない行は、自分で埋められます。 register_driver は
comm.register の双子で、引数の綴りも揃えてあります —— 一度包んでしまえば、
その先は同梱の driver と同じ扱い(名簿に出る・名簿の綴りで開く)になります。
import dynamixel_sdk
def my_dynamixel(port="COM4", baudrate=57600, **o):
return dynamixel_sdk.PortHandler(port) # 自分の装置に合わせた薄い包み
fullseye.register_driver("dynamixel", my_dynamixel,
pip="dynamixel-sdk", probe="dynamixel_sdk",
family="servo", desc="自前の Dynamixel アダプタ")
fullseye.open_driver("dynamixel", port="COM4") # 以後は名簿の綴りで開く
同梱の名簿に無い名前("my-plc" のような社内装置)も同じ口で足せます
(native=True = 外部依存なし)。表の 実装 欄が数えるのは
この版に同梱されているものなので、登録した分はそこには増えません ——
自分の環境で開けることと、配られた版が開けることは別の主張だからです。
足したものは fullseye.unregister_driver(name) で外せます(protocol 側は
comm.unregister_protocol(name))。同梱の行は外せません —— 自分が足した
ものだけが取り消しの対象で、配られた名簿が誤って空になることはありません。
差し替えを外すと、同梱の行がそのまま戻ります。
fullseye.register_driver("house-lamp", MyLamp, native=True, desc="社内の積層灯")
fullseye.open_driver("house-lamp").show("ok")
fullseye.unregister_driver("house-lamp") # -> True(名簿は元の 12 行に戻る)
fullseye.unregister_driver("io-memory") # -> False(同梱の行は外せない)
動く実物は examples/line_handshake.py(ハードは 1 つも要りません)。
シリアル線の Modbus RTU も内蔵で、実物の線が無くても組み上げられます (装置役が同じ框を解いて答えます)。
dev = fullseye.ModbusRtuLoopback(unit=1, registers={100: 7}) # 線の向こうの装置
ch = fullseye.ModbusRtuChannel(transport=dev, unit=1) # ハード不要
ch.read("holding", 100, 1) # -> [7]
# 実物の線(pyserial が要るのはここだけ)
# ch = fullseye.open_channel("modbus-rtu", port="COM3", baudrate=19200, unit=1)
同じ作法が protocol 側(fullseye.open_channel)にもあります —— 名簿は
capabilities()、扉は open_*、断るときは pip 名を言う、の 3 点セットです。
カメラ: with fullseye.Camera(0) as cam: frame = cam.grab()(USB/UVC/IP/RTSP、深度は RealSense/OAK-D 等 optional)。