Work with RecipePlan¶
For a guided, executable introduction, start with the Reusable Pipeline Recipes Learning Path. Use this how-to when you already know the task you need to complete.
RecipePlanを初めて段階的に学ぶ場合は、実行可能な Reusable Pipeline Recipes Learning Path から始めてください。具体的な作業が決まっている場合に、このHow-toを使います。
Choose WDF or Recipe¶
WDF saves one concrete, typed Frame result for later inspection or loading. Recipe saves reusable workflow intent so the same public operations can run on another input. A Recipe does not contain the original sample data; choose WDF when the result is the artifact and Recipe when the process is the artifact.
Saving or loading WDF needs the io extra; a Recipe file does not.
pip install "wandas[io]"
WDFの保存と読み込みにはio extraが必要です。Recipeファイルの保存には要りません。
What a Recipe stores¶
previousis a process-local reference to the immediately preceding Frame; it is not portable Recipe input.lineageis the complete provenance of Frame inputs.- Runtime-only callables, regex predicates, and opaque Python objects cannot be serialized into a Recipe; extraction fails explicitly at that operation.
Named Frame and external-array inputs are supplied again at replay. The serialized artifact stores operation intent and bindings, not the input samples.
Set up the examples¶
The snippets below form one small workflow. Prepare a source, a replacement Frame, and an external array before choosing the task you need:
import numpy as np
import wandas as wd
source = wd.from_numpy(
np.array([[1.0, 2.0, 4.0, 7.0]]),
sampling_rate=8_000,
ch_labels=["sensor"],
)
another_frame = wd.from_numpy(
np.array([[2.0, 5.0, 8.0, 14.0]]),
sampling_rate=8_000,
ch_labels=["sensor"],
)
external_array = np.ones((1, 4))
processed = source.remove_dc().normalize()
Extract and apply a plan¶
Process a Frame normally, then name the runtime inputs while compiling its semantic lineage:
from wandas.pipeline import RecipePlan
processed = source.remove_dc().normalize()
plan = RecipePlan.from_frame(processed, input_names=("signal",))
replayed = plan.apply({"signal": another_frame})
The names supplied during extraction must match the mapping passed to apply().
A plan with one input also takes the Frame directly, which is the same as the
mapping above:
replayed = plan.apply(another_frame)
A plan with several inputs still needs the mapping; passing one Frame raises
RecipeExecutionError listing the input names.
The result is a Frame; materialize it only at the boundary required by the task.
入力が1つのPlanは、plan.apply(another_frame)のようにFrameをそのまま渡せます。入力名の
辞書で渡すのと同じです。入力が複数のPlanにFrameを1つだけ渡すと、入力名を示した
RecipeExecutionErrorになります。
Save and load a standalone artifact¶
Save reusable operation intent separately from Frame data:
path = plan.save("analysis") # analysis.recipe.json
restored = RecipePlan.load(path)
replayed = restored.apply(another_frame)
Use to_dict() and from_dict() when the artifact must travel through another
JSON or storage layer. Saving does not overwrite an existing artifact unless
that is requested explicitly. The format-specific schema and API errors are in
the Pipeline API Reference.
Apply a saved Recipe to many recordings¶
apply_files reads each recording, applies a one-input plan, and saves each
result as WDF (it needs pip install "wandas[io]"). A recording that fails is
recorded and the rest go on; the returned pandas table has one row per
recording with path, status, output, error, and seconds.
from pathlib import Path
plan = RecipePlan.load("analysis.recipe.json")
recordings = sorted(Path("recordings").rglob("*.wav"))
table = plan.apply_files(recordings, "outputs", root="recordings", jobs=4)
table.to_csv("outputs/results.csv", index=False)
print(table[table["status"] == "error"])
root keeps each recording's folder below it (recordings/L1/a.wav becomes
outputs/L1/a.wav.wdf). jobs runs recordings in separate processes; a script
that uses it needs an if __name__ == "__main__": guard. Pass
on_error="raise" to stop at the first failure (with jobs, later recordings
may already be saved), and overwrite=True to replace earlier outputs.
保存したRecipeを多数の録音に掛けるにはapply_filesを使います。録音ごとに読み込み・実行・WDF保存を
行い、失敗した録音は記録して先に進みます。戻り値は録音ごとの成否・出力パス・エラー・所要秒の表
(pandas)です。rootを渡すとその下のフォルダ構成を保ち、jobsで別プロセスに分けて並列に
実行します(スクリプトではif __name__ == "__main__":が必要です)。on_error="raise"は最初の失敗で
止めます(jobsを使うと、後の録音がすでに保存されていることがあります)。overwrite=Trueで既存の出力を
置き換えます。
Name steps and choose which one to return¶
A plan returns one Frame per apply. To keep shared preprocessing and several
analyses of it in one Recipe, name the other Frames as steps when you extract
the plan, then choose one by name:
signal = wd.generate_sin(freqs=[1000.0], duration=0.5)
another_signal = wd.generate_sin(freqs=[500.0], duration=0.5)
weighted = signal.high_pass_filter(cutoff=50).a_weighting()
plan = RecipePlan.from_frame(
weighted.welch(),
steps={"weighted": weighted, "noct": weighted.noct_spectrum(fmin=100, fmax=5_000)},
)
spectrum = plan.apply(another_signal) # the output (welch)
octaves = plan.apply(another_signal, step="noct") # only noct is built
plan.step_names # ('weighted', 'noct')
The shared filter and A-weighting are stored once. apply(..., step=name)
builds only the operations that step needs, so steps you do not ask for are
neither computed nor kept in memory. Each call computes its own preprocessing:
asking for welch and then noct filters the input twice. Steps must come
from the same source Frames as the output.
apply_files(..., step="noct") saves that step for each recording, and
plan.for_step("noct") returns a plan with only that step's operations and
inputs to save or share on its own. apply(..., step=...) still takes every
input of the plan. A plan with steps is saved as Recipe schema 4; a plan
without steps is still saved as schema 3.
Planがapplyごとに返すFrameは1つです。共通の前処理とそれを使う複数の解析を1つのRecipeに
まとめるには、抽出するときに他のFrameをstepsとして名前付きで渡し、apply(..., step=名前)で
返す段を選びます。共通の前処理は1回だけ記録されます。選んだ段に必要な処理だけを組み立てるので、
選ばない段は計算もメモリへの保持もされません。ただし呼び出しごとに前処理を計算するので、
welchとnoctを続けて求めると前処理は2回計算されます。段は出力と同じ元のFrameから作ってください。
apply_files(..., step="noct")は録音ごとにその段を保存し、plan.for_step("noct")はその段の処理と入力だけを
持つPlanを返します(apply(..., step=...)にはPlanのすべての入力を渡します)。段を持つPlanはRecipe schema 4で保存され、段のないPlanはこれまでどおりschema 3です。
Supply multiple inputs¶
Give every runtime Frame or external array a stable name. Input order and alignment are defined by the public operation that created the plan.
processed = source + external_array
plan = RecipePlan.from_frame(processed, input_names=("signal", "offset"))
replayed = plan.apply({"signal": another_frame, "offset": external_array})
External NumPy and Dask arrays remain named inputs; they are not embedded in the artifact or wrapped in temporary Frames. At replay, supply a NumPy or Dask array with a shape and dtype accepted by the operation. The original container type and Dask chunking are not stored in the Recipe.
List operations and their parameters¶
RecipeRegistry.describe() returns one description per registered operation:
its ID, version, method name, input bindings, and the method parameters
with their defaults. Parameters annotated with Literal[...] also report their
accepted values as choices. Use it to build parameter forms, documentation, or
tool schemas from the same registry that validates and replays Recipes.
from wandas.pipeline import default_recipe_registry
catalog = default_recipe_registry().describe()
fft = next(item for item in catalog if item.method == "fft")
window = next(parameter for parameter in fft.parameters if parameter.name == "window")
print(window.default) # hann
Contracts kept only to replay older Recipes are listed too. Their method is
private (one leading underscore, such as _fft_recipe_v2); public calls record
the operations whose method is public, including operator methods such as
__add__.
Each description also says which Frame types the operation is called on
(receiver), what it returns (returns; "receiver" when the result keeps
the caller's type, such as filters), and which optional extras it needs
(extras, as in pip install "wandas[psychoacoustic]"). applies_to(frame)
checks one Frame, including a subclass that overrides the method with another
operation. For example, list what a SpectralFrame accepts, leaving out operations whose extras are missing:
from importlib.util import find_spec
needs = {"effects": "librosa", "psychoacoustic": "mosqito"}
spectrum = source.fft()
usable = [
item.method
for item in catalog
# Skip replay-only contracts (one leading "_"); keep operators such as __add__.
if not (item.method.startswith("_") and not item.method.startswith("__"))
and item.applies_to(spectrum)
and all(find_spec(needs[extra]) for extra in item.extras)
]
RecipeRegistry.describe()は登録済み操作ごとに、ID、version、メソッド名、入力の
binding、メソッドの引数と既定値を返します。Literal[...]で注釈した引数は受け付ける値を
choicesとして返します。パラメータ入力欄、ドキュメント、ツール定義を、Recipeの検証と再実行に
使うのと同じregistryから作れます。古いRecipeの再実行用に残した操作も含まれ、そのmethodは
_fft_recipe_v2のように先頭が_1つの非公開名です。公開呼出しが記録するのはmethodが公開名の
操作で、__add__のような演算子メソッドも含みます。
各説明には、その操作を呼べるFrameの型(receiver)、結果の型(returns。フィルタのように
呼んだFrameと同じ型なら"receiver")、必要なextra(extras)も入ります。applies_to(frame)は、メソッドを別の操作で上書きした
サブクラスも含めて、あるFrameにその操作を呼べるかを判定します。あるSpectralFrameに
何を呼べるかを調べたり、extraが入らない環境で使える操作を前もって絞ったりできます。
Annotate each runtime input¶
Labels, metadata and channel extra describe one recording, so a plan does
not record with_label(), with_metadata() or with_channel_extra(); replay
uses the annotations of the frame you pass in. A plan that selects channels by
extra, such as get_channel(query={"position": "front"}), therefore needs
the same annotation on every runtime input, for example from that recording's
catalog row. This example uses two-channel recordings of its own:
recording = wd.from_numpy(
np.random.default_rng(0).normal(size=(2, 800)),
sampling_rate=8_000,
ch_labels=["mic1", "mic2"],
)
annotated = recording.with_channel_extra("mic2", {"position": "front"})
front = annotated.get_channel(query={"position": "front"}).high_pass_filter(cutoff=100)
plan = RecipePlan.from_frame(front, input_names=("signal",))
next_recording = wd.from_numpy(
np.random.default_rng(1).normal(size=(2, 800)),
sampling_rate=8_000,
ch_labels=["mic1", "mic2"],
)
other = next_recording.with_channel_extra("mic2", {"position": "front"})
replayed = plan.apply({"signal": other}) # selects mic2
Replaying on a frame where no channel has the position key raises
RecipeExecutionError naming the unknown metadata key. If the key exists but
no channel has the value "front", the error says that no channel matches the
query. If a different channel carries "front", replay selects that channel,
so annotate each input from its own catalog row.
ラベル・metadata・チャネルのextraは録音ごとの情報なので、Recipeには記録されず、
再生時は渡したフレームの注釈が使われます。get_channel(query={"position": "front"})のように
extraでチャネルを選ぶRecipeは、再生する各入力にも同じ注釈(たとえばその録音の諸元表の値)を
付けてから渡してください。どのチャネルにもそのキーがない入力では、未知のメタデータキーを示す
RecipeExecutionErrorになります。キーはあっても値が一致するチャネルがなければ、クエリに
一致するチャネルがないというエラーになります。別のチャネルに同じ値を付けると、そのチャネルが
選ばれるので、注釈は各録音の諸元表から付けてください。
Find the step that failed¶
When an operation fails during apply, the RecipeExecutionError carries the
failing node as attributes, so a tool can mark that step without parsing the
message. node_id and operation are None for errors that do not belong to
one node, such as mismatched input names.
from wandas.pipeline import RecipeExecutionError
try:
plan.apply({"signal": source})
except RecipeExecutionError as error:
print(error.node_id, error.operation, repr(error.cause))
applyで処理が失敗すると、RecipeExecutionErrorのnode_id・operation・cause
(元の例外、__cause__と同じ)で失敗したノードが分かります。エラー文を読まずに、
失敗した手順へ印を付けられます。入力名の不一致のようにノードに関係しないエラーでは、
node_idとoperationはNoneです。
Handle runtime-only operations¶
Some calls intentionally cannot be portable. Arbitrary callables passed to
Frame.apply(), callable or regex channel predicates, and opaque Python objects
must remain runtime-only. Recipe extraction fails at the unsupported operation
instead of silently dropping part of the workflow.
To keep a portable operation in your own project, follow Add your own processing, including the registry passed to extraction, loading, and replay. 自分のプロジェクト内でRecipe対応の独自処理を作る場合は、 独自処理のHow-toを参照してください。
For built-in contributions and advanced Frame contracts, use the Frame and Operation extension guide.