fiftyone-develop-plugin

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Develop FiftyOne Plugins

开发FiftyOne插件

Key Directives

核心准则

ALWAYS follow these rules:
请始终遵循以下规则:

1. Understand before coding

1. 先理解再编码

Ask clarifying questions. Never assume what the plugin should do.
提出澄清问题,绝不假设插件的功能需求。

2. Plan before implementing

2. 先规划再实现

Present file structure and design. Get user approval before generating code.
展示文件结构和设计方案,在生成代码前获得用户认可。

3. Search existing plugins for patterns

3. 搜索现有插件的模式

bash
undefined
bash
undefined

Clone official plugins for reference

克隆官方插件作为参考

git clone https://github.com/voxel51/fiftyone-plugins.git /tmp/fiftyone-plugins 2>/dev/null || true
git clone https://github.com/voxel51/fiftyone-plugins.git /tmp/fiftyone-plugins 2>/dev/null || true

Search for similar patterns

搜索相似模式

grep -r "keyword" /tmp/fiftyone-plugins/plugins/ --include="*.py" -l

```python
list_plugins(enabled=True)
list_operators(builtin_only=False)
get_operator_schema(operator_uri="@voxel51/brain/compute_similarity")
grep -r "keyword" /tmp/fiftyone-plugins/plugins/ --include="*.py" -l

```python
list_plugins(enabled=True)
list_operators(builtin_only=False)
get_operator_schema(operator_uri="@voxel51/brain/compute_similarity")

4. Test locally before done

4. 完成前先本地测试

bash
undefined
bash
undefined

Get plugins directory

获取插件目录

PLUGINS_DIR=$(python -c "import fiftyone as fo; print(fo.config.plugins_dir)")
PLUGINS_DIR=$(python -c "import fiftyone as fo; print(fo.config.plugins_dir)")

Develop plugin in plugins directory

在插件目录中开发插件

cd $PLUGINS_DIR/my-plugin

Write tests:
- **Python**: `pytest` for operators/panels
- **JavaScript**: `vitest` for React components

Verify in FiftyOne App before done.
cd $PLUGINS_DIR/my-plugin

编写测试:
- **Python**:使用`pytest`测试operators/panels
- **JavaScript**:使用`vitest`测试React组件

在FiftyOne App中验证后再完成开发。

5. Iterate on feedback

5. 根据反馈迭代

Run server separately to see logs:
bash
undefined
单独运行服务器查看日志:
bash
undefined

Terminal 1: Python logs

终端1:Python日志

python -m fiftyone.server.main
python -m fiftyone.server.main

Terminal 2: Browser at localhost:5151 (JS logs in DevTools console)

终端2:在浏览器访问localhost:5151(JS日志在开发者工具控制台中)


For automated iteration, use Playwright e2e tests:
```bash
npx playwright test
Refine until the plugin works as expected.

如需自动化迭代,使用Playwright端到端测试:
```bash
npx playwright test
优化直到插件按预期工作。

Critical Patterns

关键模式

Operator Execution

Operator执行

python
undefined
python
undefined

Chain operators (non-delegated operators only, in execute() only, fire-and-forget)

链式调用operators(仅非委托operators,仅在execute()中使用,即发即弃)

ctx.trigger("@plugin/other_operator", params={...})
ctx.trigger("@plugin/other_operator", params={...})

UI operations

UI操作

ctx.ops.notify("Done!") ctx.ops.set_progress(0.5)
undefined
ctx.ops.notify("Done!") ctx.ops.set_progress(0.5)
undefined

View Selection

视图选择

python
undefined
python
undefined

Use ctx.target_view() to respect user's current selection and filters

使用ctx.target_view()以尊重用户当前的选择和过滤器

view = ctx.target_view()
view = ctx.target_view()

ctx.dataset - Full dataset (use when explicitly exporting all)

ctx.dataset - 完整数据集(明确导出全部数据时使用)

ctx.view - Filtered view (use for read-only operations)

ctx.view - 过滤后的视图(用于只读操作)

ctx.target_view() - Filtered + selected samples (use for exports/processing)

ctx.target_view() - 过滤后+选中的样本(用于导出/处理)

undefined
undefined

Store Keys (Avoid Collisions)

存储键(避免冲突)

python
undefined
python
undefined

Use namespaced keys to avoid cross-dataset conflicts

使用命名空间键避免跨数据集冲突

def _get_store_key(self, ctx): plugin_name = self.config.name.split("/")[-1] return f"{plugin_name}store{ctx.dataset.doc.id}{self.version}"
store = ctx.store(self._get_store_key(ctx))
undefined
def _get_store_key(self, ctx): plugin_name = self.config.name.split("/")[-1] return f"{plugin_name}store{ctx.dataset.doc.id}{self.version}"
store = ctx.store(self._get_store_key(ctx))
undefined

Panel State vs Execution Store

Panel状态与执行存储

python
undefined
python
undefined

ctx.panel.state - Transient (resets when panel reloads)

ctx.panel.state - 临时存储(面板重新加载时重置)

ctx.store() - Persistent (survives across sessions)

ctx.store() - 持久存储(跨会话保留)

def on_load(self, ctx): ctx.panel.state.selected_tab = "overview" # Transient store = ctx.store(self._get_store_key(ctx)) ctx.panel.state.config = store.get("user_config") or {} # Persistent
undefined
def on_load(self, ctx): ctx.panel.state.selected_tab = "overview" # 临时 store = ctx.store(self._get_store_key(ctx)) ctx.panel.state.config = store.get("user_config") or {} # 持久
undefined

Delegated Execution

委托执行

Use for operations that: process >100 samples or take >1 second.
python
@property
def config(self):
    return foo.OperatorConfig(
        name="heavy_operator",
        allow_delegated_execution=True,
        default_choice_to_delegated=True,
    )
适用于处理超过100个样本或耗时超过1秒的操作。
python
@property
def config(self):
    return foo.OperatorConfig(
        name="heavy_operator",
        allow_delegated_execution=True,
        default_choice_to_delegated=True,
    )

Progress Reporting

进度报告

python
@property
def config(self):
    return foo.OperatorConfig(
        name="progress_operator",
        execute_as_generator=True,
    )

def execute(self, ctx):
    total = len(ctx.target_view())
    for i, sample in enumerate(ctx.target_view()):
        # Process sample...
        yield ctx.trigger("set_progress", {"progress": (i+1)/total})
    yield {"status": "complete"}
python
@property
def config(self):
    return foo.OperatorConfig(
        name="progress_operator",
        execute_as_generator=True,
    )

def execute(self, ctx):
    total = len(ctx.target_view())
    for i, sample in enumerate(ctx.target_view()):
        # 处理样本...
        yield ctx.trigger("set_progress", {"progress": (i+1)/total})
    yield {"status": "complete"}

Custom Runs (Auditability)

自定义运行(可审计性)

Use Custom Runs for operations needing reproducibility and history tracking:
python
undefined
对于需要可复现性和历史跟踪的操作,使用自定义运行:
python
undefined

Create run key (must be valid Python identifier - use underscores, not slashes)

创建运行键(必须是有效的Python标识符 - 使用下划线,不要用斜杠)

run_key = f"my_plugin_{self.config.name}v1{timestamp}"
run_key = f"my_plugin_{self.config.name}v1{timestamp}"

Initialize and register

初始化并注册

run_config = ctx.dataset.init_run(operator=self.config.name, params=ctx.params) ctx.dataset.register_run(run_key, run_config)

See [PYTHON-OPERATOR.md](PYTHON-OPERATOR.md#custom-runs-auditable-operations) for full Custom Runs pattern.
See [EXECUTION-STORE.md](EXECUTION-STORE.md) for advanced caching patterns.
See [HYBRID-PLUGINS.md](HYBRID-PLUGINS.md) for Python + JavaScript communication.
run_config = ctx.dataset.init_run(operator=self.config.name, params=ctx.params) ctx.dataset.register_run(run_key, run_config)

查看[PYTHON-OPERATOR.md](PYTHON-OPERATOR.md#custom-runs-auditable-operations)获取完整的自定义运行模式。
查看[EXECUTION-STORE.md](EXECUTION-STORE.md)获取高级缓存模式。
查看[HYBRID-PLUGINS.md](HYBRID-PLUGINS.md)了解Python + JavaScript通信方式。

Workflow

工作流程

Phase 1: Requirements

阶段1:需求分析

Understand what the user needs to accomplish:
  1. "What problem are you trying to solve?"
  2. "What should the user be able to do?" (user's perspective)
  3. "What information does the user provide?"
  4. "What result does the user expect to see?"
  5. "Any external data sources or services involved?"
  6. "How will this fit into the user's workflow?"
理解用户需要实现的目标:
  1. "你要解决什么问题?"
  2. "用户应该能够执行哪些操作?"(用户视角)
  3. "用户需要提供哪些信息?"
  4. "用户期望看到什么结果?"
  5. "是否涉及外部数据源或服务?"
  6. "这将如何融入用户的工作流?"

Phase 2: Design

阶段2:设计

  1. Search existing plugins for similar patterns
  2. For panels, default to hybrid (Python + JavaScript). See HYBRID-PLUGINS.md.
  3. Create plan with:
    • Plugin name (
      @org/plugin-name
      )
    • File structure
    • Operator/panel specs
    • Input/output definitions
  4. Get user approval before coding
See PLUGIN-STRUCTURE.md for file formats.
  1. 搜索现有插件的相似模式
  2. 对于面板,默认使用混合模式(Python + JavaScript)。查看HYBRID-PLUGINS.md
  3. 创建包含以下内容的方案:
    • 插件名称(
      @org/plugin-name
    • 文件结构
    • Operator/面板规格
    • 输入/输出定义
  4. 编码前获得用户认可
查看PLUGIN-STRUCTURE.md了解文件格式。

Phase 3: Generate Code

阶段3:生成代码

Create these files:
FileRequiredPurpose
fiftyone.yml
YesPlugin manifest
__init__.py
YesPython operators/panels
requirements.txt
If depsPython dependencies
package.json
JS onlyNode.js metadata
src/index.tsx
JS onlyReact components
Reference docs:
  • PYTHON-OPERATOR.md - Python operators
  • PYTHON-PANEL.md - Python panels
  • JAVASCRIPT-PANEL.md - React/TypeScript panels
  • HYBRID-PLUGINS.md - Python + JavaScript communication
  • EXECUTION-STORE.md - Persistent storage and caching
For JavaScript panels with rich UI: Invoke the
fiftyone-voodo-design
skill for VOODO components (buttons, inputs, toasts, design tokens). VOODO is FiftyOne's official React component library.
创建以下文件:
文件是否必需用途
fiftyone.yml
插件清单
__init__.py
Python operators/面板
requirements.txt
有依赖时Python依赖
package.json
仅JSNode.js元数据
src/index.tsx
仅JSReact组件
参考文档:
  • PYTHON-OPERATOR.md - Python operators
  • PYTHON-PANEL.md - Python面板
  • JAVASCRIPT-PANEL.md - React/TypeScript面板
  • HYBRID-PLUGINS.md - Python + JavaScript通信
  • EXECUTION-STORE.md - 持久存储和缓存
对于具有丰富UI的JavaScript面板:调用
fiftyone-voodo-design
技能获取VOODO组件(按钮、输入框、提示框、设计令牌)。VOODO是FiftyOne官方的React组件库。

Phase 4: Validate & Test

阶段4:验证与测试

4.1 Validate Detection

4.1 验证插件检测

python
list_plugins(enabled=True)  # Should show your plugin
list_operators()  # Should show your operators
If not found: Check fiftyone.yml syntax, Python syntax errors, restart App.
python
list_plugins(enabled=True)  # 应显示你的插件
list_operators()  # 应显示你的operators
如果未找到:检查fiftyone.yml语法、Python语法错误,重启App。

4.2 Validate Schema

4.2 验证Schema

python
get_operator_schema(operator_uri="@myorg/my-operator")
Verify inputs/outputs match your expectations.
python
get_operator_schema(operator_uri="@myorg/my-operator")
验证输入/输出是否符合预期。

4.3 Test Execution

4.3 测试执行

python
set_context(dataset_name="test-dataset")
launch_app()
execute_operator(operator_uri="@myorg/my-operator", params={...})
Common failures:
  • "Operator not found" → Check fiftyone.yml operators list
  • "Missing parameter" → Check resolve_input() required fields
  • "Execution error" → Check execute() implementation
python
set_context(dataset_name="test-dataset")
launch_app()
execute_operator(operator_uri="@myorg/my-operator", params={...})
常见失败原因
  • "Operator not found" → 检查fiftyone.yml中的operators列表
  • "Missing parameter" → 检查resolve_input()的必填字段
  • "Execution error" → 检查execute()实现

Phase 5: Iterate

阶段5:迭代

  1. Get user feedback
  2. Fix issues (sync source and plugins directory if separate)
  3. Restart App if needed
  4. Repeat until working
  1. 获取用户反馈
  2. 修复问题(如果源码和插件目录分离,需同步)
  3. 必要时重启App
  4. 重复直到插件正常工作

Quick Reference

快速参考

Plugin Types

插件类型

TypeLanguageUse Case
OperatorPythonData processing, computations
PanelHybrid (default)Python backend + React frontend (recommended)
PanelPython-onlySimple UI without rich interactivity
类型语言适用场景
OperatorPython数据处理、计算
Panel混合模式(默认)Python后端 + React前端(推荐)
Panel纯Python无丰富交互的简单UI

Operator Config Options

Operator配置选项

OptionDefaultEffect
dynamic
FalseRecalculate inputs on change
execute_as_generator
FalseStream progress with yield
allow_immediate_execution
TrueExecute in foreground
allow_delegated_execution
FalseBackground execution
default_choice_to_delegated
FalseDefault to background
unlisted
FalseHide from operator browser
on_startup
FalseExecute when app starts
on_dataset_open
FalseExecute when dataset opens
选项默认值作用
dynamic
False变更时重新计算输入
execute_as_generator
False通过yield流式返回进度
allow_immediate_execution
True前台执行
allow_delegated_execution
False后台执行
default_choice_to_delegated
False默认后台执行
unlisted
False在operator浏览器中隐藏
on_startup
FalseApp启动时执行
on_dataset_open
False数据集打开时执行

Panel Config Options

Panel配置选项

OptionDefaultEffect
allow_multiple
FalseAllow multiple panel instances
surfaces
"grid"Where panel can display ("grid", "modal", "grid modal")
category
NonePanel category in browser
priority
NoneSort order in UI
选项默认值作用
allow_multiple
False允许多个面板实例
surfaces
"grid"面板可显示的位置("grid", "modal", "grid modal")
category
None面板在浏览器中的分类
priority
NoneUI中的排序顺序

Input Types

输入类型

TypeMethod
Text
inputs.str()
Number
inputs.int()
/
inputs.float()
Boolean
inputs.bool()
Dropdown
inputs.enum()
File
inputs.file()
View
inputs.view_target()
类型方法
文本
inputs.str()
数字
inputs.int()
/
inputs.float()
布尔值
inputs.bool()
下拉框
inputs.enum()
文件
inputs.file()
视图
inputs.view_target()

Minimal Example

最简示例

fiftyone.yml:
yaml
name: "@myorg/hello-world"
type: plugin
operators:
  - hello_world
init.py:
python
import fiftyone.operators as foo
import fiftyone.operators.types as types

class HelloWorld(foo.Operator):
    @property
    def config(self):
        return foo.OperatorConfig(
            name="hello_world",
            label="Hello World"
        )

    def resolve_input(self, ctx):
        inputs = types.Object()
        inputs.str("message", label="Message", default="Hello!")
        return types.Property(inputs)

    def execute(self, ctx):
        print(ctx.params["message"])
        return {"status": "done"}

def register(p):
    p.register(HelloWorld)
fiftyone.yml:
yaml
name: "@myorg/hello-world"
type: plugin
operators:
  - hello_world
init.py:
python
import fiftyone.operators as foo
import fiftyone.operators.types as types

class HelloWorld(foo.Operator):
    @property
    def config(self):
        return foo.OperatorConfig(
            name="hello_world",
            label="Hello World"
        )

    def resolve_input(self, ctx):
        inputs = types.Object()
        inputs.str("message", label="Message", default="Hello!")
        return types.Property(inputs)

    def execute(self, ctx):
        print(ctx.params["message"])
        return {"status": "done"}

def register(p):
    p.register(HelloWorld)

Debugging

调试

Where Logs Go

日志位置

Log TypeLocation
Python backendTerminal running the server
JavaScript frontendBrowser console (F12 → Console)
Network requestsBrowser DevTools (F12 → Network)
Operator errorsOperator browser in FiftyOne App
日志类型位置
Python后端运行服务器的终端
JavaScript前端浏览器控制台(F12 → Console)
网络请求浏览器开发者工具(F12 → Network)
Operator错误FiftyOne App中的operator浏览器

Running Server Separately (Recommended for Development)

单独运行服务器(开发推荐)

To see Python plugin logs, run the server and app separately:
bash
undefined
要查看Python插件日志,单独运行服务器和App:
bash
undefined

Terminal 1: Run FiftyOne server (shows Python logs)

终端1:运行FiftyOne服务器(显示Python日志)

python -m fiftyone.server.main
python -m fiftyone.server.main

Terminal 2: Access the app in browser

终端2:在浏览器中访问App

Logs from print() and logging will appear in Terminal 1

print()和logging的日志将显示在终端1中

undefined
undefined

Python Debugging

Python调试

python
def execute(self, ctx):
    # Use print() for quick debugging (shows in server terminal)
    print(f"Params received: {ctx.params}")
    print(f"Dataset: {ctx.dataset.name}, View size: {len(ctx.view)}")

    # For structured logging
    import logging
    logging.info(f"Processing {len(ctx.target_view())} samples")

    # ... rest of execution
python
def execute(self, ctx):
    # 使用print()快速调试(显示在服务器终端)
    print(f"Params received: {ctx.params}")
    print(f"Dataset: {ctx.dataset.name}, View size: {len(ctx.view)}")

    # 结构化日志
    import logging
    logging.info(f"Processing {len(ctx.target_view())} samples")

    # ... 剩余执行代码

JavaScript/TypeScript Debugging

JavaScript/TypeScript调试

typescript
// Use console.log in React components
console.log("Component state:", state);
console.log("Panel data:", panelData);

// Check browser DevTools:
// - Console: JS errors, syntax errors, plugin load failures
// - Network: API calls, variable values before/after execution
typescript
// 在React组件中使用console.log
console.log("Component state:", state);
console.log("Panel data:", panelData);

// 检查浏览器开发者工具:
// - Console: JS错误、语法错误、插件加载失败
// - Network: API调用、执行前后的变量值

Common Debug Locations

常见调试位置

  • Operator not executing: Check Network tab for request/response
  • Plugin not loading: Check Console for syntax errors
  • Variables not updating: Check Network tab for payload data
  • Silent failures: Check Operator browser for error messages
  • Operator未执行:检查Network标签页的请求/响应
  • 插件未加载:检查Console中的语法错误
  • 变量未更新:检查Network标签页的负载数据
  • 静默失败:检查Operator浏览器中的错误消息

Troubleshooting

故障排除

Plugin not appearing:
  • Check
    fiftyone.yml
    exists in plugin root
  • Verify location:
    ~/.fiftyone/plugins/
  • Check for Python syntax errors
  • Restart FiftyOne App
Operator not found:
  • Verify operator listed in
    fiftyone.yml
  • Check
    register()
    function
  • Run
    list_operators()
    to debug
Secrets not available:
  • Add to
    fiftyone.yml
    under
    secrets:
  • Set environment variables before starting FiftyOne
插件未显示:
  • 检查插件根目录是否存在
    fiftyone.yml
  • 验证位置:
    ~/.fiftyone/plugins/
  • 检查Python语法错误
  • 重启FiftyOne App
Operator未找到:
  • 验证
    fiftyone.yml
    中是否列出该operator
  • 检查
    register()
    函数
  • 运行
    list_operators()
    调试
密钥不可用:
  • fiftyone.yml
    secrets:
    下添加
  • 启动FiftyOne前设置环境变量

Advanced

高级功能

Programmatic Operator Execution

程序化执行Operator

python
undefined
python
undefined

For executing operators outside of FiftyOne App context

在FiftyOne App上下文外执行operator

import fiftyone.operators as foo result = foo.execute_operator(operator_uri, ctx, **params)
undefined
import fiftyone.operators as foo result = foo.execute_operator(operator_uri, ctx, **params)
undefined

Resources

资源