Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@ on:
- 'scripts/generate_supported_formats_doc.py'
- 'scripts/generate_tool_registry_doc.py'
- 'scripts/generate_spec_v2_reference.py'
- 'scripts/generate_upscayl_models_doc.py'
- 'schemas/renderflow-v2.schema.json'
- 'examples/renderflow-v2.yaml'
- 'crates/renderflow-core/data/tool-registry.yaml'
- 'crates/renderflow-core/data/upscayl-models.yaml'
- 'crates/renderflow-core/src/audio/format.rs'
- 'crates/renderflow-core/src/graph/format.rs'
- 'crates/renderflow-core/src/image/format.rs'
- 'crates/renderflow-core/src/input_format.rs'
- 'crates/renderflow-core/src/spec.rs'
- 'crates/renderflow-core/src/super_resolution.rs'
- 'crates/renderflow-core/src/commands/spec.rs'
- 'crates/renderflow-core/src/toolchain.rs'
push:
Expand All @@ -33,14 +36,17 @@ on:
- 'scripts/generate_supported_formats_doc.py'
- 'scripts/generate_tool_registry_doc.py'
- 'scripts/generate_spec_v2_reference.py'
- 'scripts/generate_upscayl_models_doc.py'
- 'schemas/renderflow-v2.schema.json'
- 'examples/renderflow-v2.yaml'
- 'crates/renderflow-core/data/tool-registry.yaml'
- 'crates/renderflow-core/data/upscayl-models.yaml'
- 'crates/renderflow-core/src/audio/format.rs'
- 'crates/renderflow-core/src/graph/format.rs'
- 'crates/renderflow-core/src/image/format.rs'
- 'crates/renderflow-core/src/input_format.rs'
- 'crates/renderflow-core/src/spec.rs'
- 'crates/renderflow-core/src/super_resolution.rs'
- 'crates/renderflow-core/src/commands/spec.rs'
- 'crates/renderflow-core/src/toolchain.rs'
workflow_dispatch:
Expand Down Expand Up @@ -82,6 +88,7 @@ jobs:
run: |
python scripts/generate_supported_formats_doc.py
python scripts/generate_tool_registry_doc.py
python scripts/generate_upscayl_models_doc.py
cargo run --quiet -p renderflow-cli -- spec schema --format json --output schemas/renderflow-v2.schema.json
python scripts/generate_spec_v2_reference.py

Expand All @@ -90,6 +97,7 @@ jobs:
git diff --exit-code -- \
docs/user-guide/supported-formats.md \
docs/user-guide/tool-registry.md \
docs/user-guide/upscayl-models.md \
docs/user-guide/spec-v2-reference.md \
schemas/renderflow-v2.schema.json

Expand Down Expand Up @@ -132,6 +140,7 @@ jobs:
run: |
python scripts/generate_supported_formats_doc.py
python scripts/generate_tool_registry_doc.py
python scripts/generate_upscayl_models_doc.py
cargo run --quiet -p renderflow-cli -- spec schema --format json --output schemas/renderflow-v2.schema.json
python scripts/generate_spec_v2_reference.py

Expand Down
19 changes: 19 additions & 0 deletions crates/renderflow-core/data/tool-registry.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,22 @@ tools:
support_tier: experimental
license_notes: "Ghostscript is available under AGPL/commercial licensing; review distribution obligations."
distribution_notes: "Used by press-oriented TIFF/PDF command adapters when configured."


- id: tool.upscayl-ncnn
name: Upscayl NCNN
discovery:
kind: executable
candidates: [upscayl-ncnn, upscayl-bin]
version_args: [-h]
operating_systems: [linux, macos, windows]
capabilities:
- image.super_resolution
input_media_types: [image/*]
output_media_types: [image/*]
determinism: configuration_dependent
locality: local
fidelity: lossy
support_tier: experimental
license_notes: "The Upscayl NCNN backend is AGPL-3.0; model licensing is separate and recorded per model variant."
distribution_notes: "Requires compatible Vulkan/GPU runtime and NCNN .param/.bin model pairs; packaged desktop distributions may expose the executable as upscayl-bin."
58 changes: 58 additions & 0 deletions crates/renderflow-core/data/upscayl-models.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
schema: renderflow.upscayl-models/v1

models:
- variant_id: variant.upscayl-ncnn.upscayl-standard-4x
model_name: upscayl-standard-4x
display_name: Standard
native_scale: 4
commercial_use: unknown
description: General-purpose Upscayl super-resolution model.
license_notes: "Model-specific usage terms are not asserted by Renderflow; verify the model license before commercial publication."

- variant_id: variant.upscayl-ncnn.upscayl-lite-4x
model_name: upscayl-lite-4x
display_name: Lite
native_scale: 4
commercial_use: unknown
description: Lighter-weight general-purpose Upscayl super-resolution model.
license_notes: "Model-specific usage terms are not asserted by Renderflow; verify the model license before commercial publication."

- variant_id: variant.upscayl-ncnn.high-fidelity-4x
model_name: high-fidelity-4x
display_name: High Fidelity
native_scale: 4
commercial_use: unknown
description: Upscayl model oriented toward preserving source appearance and detail.
license_notes: "Model-specific usage terms are not asserted by Renderflow; verify the model license before commercial publication."

- variant_id: variant.upscayl-ncnn.remacri-4x
model_name: remacri-4x
display_name: Remacri
native_scale: 4
commercial_use: prohibited
description: Remacri model distributed in the Upscayl default model set.
license_notes: "Upscayl currently labels this model Non-Commercial. Do not select it for commercial publication without separately establishing permission."

- variant_id: variant.upscayl-ncnn.ultramix-balanced-4x
model_name: ultramix-balanced-4x
display_name: Ultramix Balanced
native_scale: 4
commercial_use: prohibited
description: Balanced Ultramix model distributed in the Upscayl default model set.
license_notes: "Upscayl currently labels this model Non-Commercial. Do not select it for commercial publication without separately establishing permission."

- variant_id: variant.upscayl-ncnn.ultrasharp-4x
model_name: ultrasharp-4x
display_name: Ultrasharp
native_scale: 4
commercial_use: prohibited
description: Sharpness-oriented model distributed in the Upscayl default model set.
license_notes: "Upscayl currently labels this model Non-Commercial. Do not select it for commercial publication without separately establishing permission."

- variant_id: variant.upscayl-ncnn.digital-art-4x
model_name: digital-art-4x
display_name: Digital Art
native_scale: 4
commercial_use: unknown
description: Upscayl model oriented toward illustrations and digital artwork.
license_notes: "Model-specific usage terms are not asserted by Renderflow; verify the model license before commercial publication."
5 changes: 5 additions & 0 deletions crates/renderflow-core/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ pub fn run_cli(cli: Cli) -> Result<()> {
format,
transforms,
} => commands::tools::run_inspect(&id, transforms.as_deref(), &format)?,
ToolCommands::Variants {
id,
models_dir,
format,
} => commands::tools::run_variants(&id, models_dir.as_deref(), &format)?,
},
Some(Commands::Capabilities { format, transforms }) => {
commands::tools::run_capabilities(transforms.as_deref(), &format)?
Expand Down
12 changes: 12 additions & 0 deletions crates/renderflow-core/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,18 @@ pub enum ToolCommands {
#[arg(long, value_name = "FILE")]
transforms: Option<String>,
},

/// List stable provider variants/models and optional runtime material evidence.
Variants {
/// Stable provider/tool identifier, for example `tool.upscayl-ncnn`.
id: String,
/// Optional provider model/material directory used for runtime discovery.
#[arg(long, value_name = "DIR")]
models_dir: Option<String>,
/// Output format: text (default), json, or yaml.
#[arg(long, default_value = "text", value_name = "FORMAT")]
format: String,
},
}

/// Subcommands for `renderflow ai`.
Expand Down
43 changes: 43 additions & 0 deletions crates/renderflow-core/src/commands/tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::collections::BTreeMap;
use anyhow::{Context, Result};
use serde::Serialize;

use crate::super_resolution::{UpscaylModelCatalog, UPSCAYL_TOOL_ID};
use crate::toolchain::{ToolAvailability, ToolDescriptor, ToolRegistry};
use crate::transforms::yaml_loader::load_tool_registry_from_yaml;

Expand Down Expand Up @@ -141,6 +142,48 @@ pub fn run_inspect(id: &str, transforms: Option<&str>, format: &str) -> Result<(
Ok(())
}

pub fn run_variants(id: &str, models_dir: Option<&str>, format: &str) -> Result<()> {
if id != UPSCAYL_TOOL_ID {
anyhow::bail!("tool provider '{id}' does not expose a registered variant catalog");
}
let catalog = match models_dir {
Some(path) => UpscaylModelCatalog::discover(path)
.with_context(|| format!("failed to discover Upscayl models from '{path}'"))?,
None => UpscaylModelCatalog::builtins(),
};
let format = StructuredFormat::parse(format)?;
if format != StructuredFormat::Text {
return emit_serialized(&catalog, format);
}

println!("Tool variants: {id}");
println!("===============================");
for model in &catalog.models {
let digest = model.model_digest.as_deref().unwrap_or("-");
let scale = model
.native_scale
.map(|value| format!("x{value}"))
.unwrap_or_else(|| "unknown".to_string());
println!(
"{:<52} {:<24} {:<8} {:<12} {:<10} {}",
model.variant_id,
model.model_name,
scale,
model.commercial_use.as_str(),
if model.is_materialized() {
"ready"
} else {
"catalog"
},
digest
);
}
for diagnostic in &catalog.diagnostics {
println!("diagnostic [{}]: {}", diagnostic.code, diagnostic.message);
}
Ok(())
}

pub fn run_capabilities(transforms: Option<&str>, format: &str) -> Result<()> {
let registry = load_registry(transforms)?;
let capabilities: BTreeMap<String, Vec<String>> = registry.capabilities();
Expand Down
47 changes: 46 additions & 1 deletion crates/renderflow-core/src/graph/execution_plan.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::collections::{BTreeMap, HashSet};

use super::{Format, InputKind, MultiTargetDag, TransformEdge};
use crate::optimization::OptimizationMode;
Expand Down Expand Up @@ -67,6 +67,12 @@ pub struct PlanEdge {
/// Stable capability identifier implemented by the selected provider.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub capability_id: Option<String>,
/// Stable provider-specific transform/model variant identity.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub variant_id: Option<String>,
/// Variant-specific reproducibility evidence.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub evidence: BTreeMap<String, String>,
}

impl PlanEdge {
Expand All @@ -91,6 +97,8 @@ impl PlanEdge {
edge_type,
provider_id: e.provider_id.clone(),
capability_id: e.capability_id.clone(),
variant_id: e.variant_id.clone(),
evidence: e.evidence.clone(),
}
}
}
Expand Down Expand Up @@ -644,6 +652,43 @@ mod tests {
assert_eq!(plan.metadata.execution_depth, 1);
}

#[test]
fn test_plan_preserves_provider_variant_evidence() {
let mut graph = TransformGraph::new();
graph.add_transform(
TransformEdge::new(Format::Png, Format::Webp, 1.0, 0.9)
.with_provider("tool.upscayl-ncnn", "image.super_resolution")
.with_variant("variant.upscayl-ncnn.digital-art-4x")
.with_evidence("model_digest", "sha256:model"),
);
let dag = graph
.build_multi_target_dag(Format::Png, &[Format::Webp])
.unwrap();
let plan = ExecutionPlan::from_dag(
&dag,
Format::Png,
&[Format::Webp],
OptimizationMode::Balanced,
);

assert_eq!(plan.edges.len(), 1);
let edge = &plan.edges[0];
assert_eq!(edge.provider_id.as_deref(), Some("tool.upscayl-ncnn"));
assert_eq!(
edge.capability_id.as_deref(),
Some("image.super_resolution")
);
assert_eq!(
edge.variant_id.as_deref(),
Some("variant.upscayl-ncnn.digital-art-4x")
);
assert_eq!(
edge.evidence.get("model_digest").map(String::as_str),
Some("sha256:model")
);
assert_eq!(plan.waves[0].edges[0].variant_id, edge.variant_id);
}

#[test]
fn test_plan_serialization_roundtrip() {
let plan = build_plan();
Expand Down
25 changes: 25 additions & 0 deletions crates/renderflow-core/src/graph/transform_edge.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::BTreeMap;

use super::{Format, InputKind};

/// Metadata attached to a directed edge in the [`TransformGraph`](super::TransformGraph).
Expand Down Expand Up @@ -26,6 +28,10 @@ pub struct TransformEdge {
pub provider_id: Option<String>,
/// Stable machine-readable capability identifier for this edge, when known.
pub capability_id: Option<String>,
/// Stable provider-specific transform/model variant identity, when known.
pub variant_id: Option<String>,
/// Deterministically ordered evidence that affects this variant's reproducibility.
pub evidence: BTreeMap<String, String>,
}

impl TransformEdge {
Expand All @@ -47,6 +53,8 @@ impl TransformEdge {
input_kind: InputKind::Single,
provider_id: None,
capability_id: None,
variant_id: None,
evidence: BTreeMap::new(),
}
}

Expand Down Expand Up @@ -76,6 +84,8 @@ impl TransformEdge {
input_kind,
provider_id: None,
capability_id: None,
variant_id: None,
evidence: BTreeMap::new(),
}
}

Expand All @@ -89,6 +99,21 @@ impl TransformEdge {
self.capability_id = Some(capability_id.into());
self
}

pub fn with_variant(mut self, variant_id: impl Into<String>) -> Self {
self.variant_id = Some(variant_id.into());
self
}

pub fn with_evidence(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.evidence.insert(key.into(), value.into());
self
}

pub fn with_evidence_map(mut self, evidence: BTreeMap<String, String>) -> Self {
self.evidence = evidence;
self
}
}

#[cfg(test)]
Expand Down
1 change: 1 addition & 0 deletions crates/renderflow-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub mod process;
mod sdk;
pub mod spec;
pub mod strategies;
pub mod super_resolution;
mod template;
pub mod toolchain;
pub mod transforms;
Expand Down
Loading
Loading